diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 79% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index cc00b9f..86eed15 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,8 +2,8 @@ Read the following before reporting a bug on GitHub: -1. Update to the newest version of Lychee -2. Update your Browser to the newest version +1. Update Lychee to the newest version +2. Update your browser to the newest version 2. Take a look in the [FAQ](https://github.com/electerious/Lychee/blob/master/docs/FAQ.md) 3. Check if someone has [already reported](https://github.com/electerious/Lychee/issues) the same bug @@ -12,7 +12,7 @@ When reporting a bug on GitHub, make sure you include the following information: - Detailed description of the problem - How to reproduce the issue (step-by-step) - What you have already tried -- Output of the diagnostics (`plugins/check/index.php`) +- Output of the diagnostics (`plugins/Diagnostics/index.php`) - Browser and system version - Attach files when you have problems which specific photos @@ -23,15 +23,15 @@ Check if there are branches newer than `master`. Always fork the newest availabl Please follow the conventions already established in the code. - **Spacing**:
- Use tabs for indentation. No spaces. + Use tabs for indentation. Spaces for alignment. - **Naming**:
Keep variable and method names concise and descriptive. - **Quotes**:
Single-quoted strings are preferred to double-quoted strings - + - **Comments**:
- Please use single-line comments to annotate significant additions. Use `#` for comments in PHP; `//` for comments in JS and CSS. - -Merge your changes when the forked branch has been updated in the meanwhile. Make sure your code is 100% working before creating a Pull-Request on GitHub. + Please use single-line comments to annotate significant additions. Use `//` for comments in PHP, JS and CSS. + +Merge your changes when the forked branch has been updated in the meanwhile. Make sure your code is 100% working before creating a Pull-Request on GitHub. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..5a2c813 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +### Detailed description of the problem + +### Steps to reproduce the issue + +### Output of the diagnostics (Settings => Diagnostics) + +### Browser and system \ No newline at end of file diff --git a/.gitignore b/.gitignore index 254c796..7763479 100644 --- a/.gitignore +++ b/.gitignore @@ -7,12 +7,13 @@ uploads/big/* uploads/import/* uploads/medium/* uploads/thumb/* -plugins/* !uploads/big/index.html !uploads/import/index.html !uploads/medium/index.html !uploads/thumb/index.html -!plugins/check/ -!plugins/displaylog/ \ No newline at end of file +plugins/* + +!plugins/Diagnostics/ +!plugins/Log/ \ No newline at end of file diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/README.md b/README.md index 388ed11..e7d1b2a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Lychee is a free photo-management tool, which runs on your server or web-space. ## Installation -To run Lychee, everything you need is a web-server with PHP 5.3 or later and a MySQL-Database. Follow the instructions to install Lychee on your server. [Installation »](docs/Installation.md) +To run Lychee, everything you need is a web-server with PHP 5.5 or later and a MySQL-Database. Follow the instructions to install Lychee on your server. [Installation »](docs/Installation.md) ## How to use @@ -65,6 +65,7 @@ Here's a list of all available Plugins and Extensions: | lychee-rss | Creates a RSS-Feed out of your photos | [More »](https://github.com/cternes/Lychee-RSS) | | lychee-FlashAir | Import from a Toshiba FlashAir WiFi SD card | [More »](https://github.com/mhp/Lychee-FlashAir) | | lychee-webroot | Controls photos accessibility and keeps Lychee files hidden | [More »](https://github.com/Bramas/lychee-webroot) | +| lychee-create-medium | Generate missing medium size photos | [More »](https://github.com/Bramas/lychee-create-medium) | ## Troubleshooting diff --git a/dist/main.css b/dist/main.css index 63c0582..4b77b87 100755 Binary files a/dist/main.css and b/dist/main.css differ diff --git a/dist/main.js b/dist/main.js old mode 100644 new mode 100755 index cb57089..c66a317 Binary files a/dist/main.js and b/dist/main.js differ diff --git a/dist/view.js b/dist/view.js index 388f4a0..e4b79d2 100644 Binary files a/dist/view.js and b/dist/view.js differ diff --git a/docs/Build.md b/docs/Build.md index 6d9af87..678f2a6 100644 --- a/docs/Build.md +++ b/docs/Build.md @@ -2,24 +2,20 @@ First you have to install the following dependencies: -- `node` [Node.js](http://nodejs.org) v0.10 or later +- `node` [Node.js](http://nodejs.org) v5.7.0 or later - `npm` [Node Packaged Modules](https://www.npmjs.org) -- `gulp` [Gulp.js](http://gulpjs.com) -After [installing Node.js](http://nodejs.org) you can use the included `npm` package manager to install the global requirements and Lychee-dependencies with the following command: +After [installing Node.js](http://nodejs.org) you can use the included `npm` package manager to download all dependencies: cd src/ - npm install -g gulp npm install ### Build -The Gulpfile is located in `src/` and can be easily executed using the `gulp` command. +The Gulpfile is located in `src/` and can be executed using the `npm run compile` command. ### Watch for changes -While developing, you might want to use the following command to watch for changes: +While developing, you might want to use the following command to automatically build Lychee everytime you save a file: - gulp watch - -`gulp watch` will automatically build Lychee everytime you save a file. + npm start \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index 2ad8e32..7567d8b 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,3 +1,38 @@ +## v3.1.0 + +Released March ??, 2016 + +**Warning**: It's no longer possible to update from Lychee versions older than 2.7. + +**Warning**: Plugins which use the plugin API of Lychee must be updated to work with the new back-end. + +**Notice**: It's no longer possible to edit the thumb quality in the database. + +**Notice**: It's no longer possible to disable the creation of medium-sized photos when Imagick is installed on the system. + +This updates includes a huge rewrite of the back-end. We are now using namespaces and the singleton pattern for Settings::get(), Database::get() and Plugins::get(). Everything is way better documented thanks to PHPDoc comments. Ugly `#` comments have been replaced with the more known `//`. Unused functions are gone and returns are more strict. We also added a handy module to output messages. Failed database updates and invalid queries will be saved to the log. + +- `New` Empty titles for albums +- `New` Share albums as hidden so they are only viewable with a direct link (#27) +- `New` Log failed and successful login attempts (Thanks @qligier, #382 #246) +- `Improved` error messages and log output +- `Improved` The search shows albums above photos (#434) +- `Improved` Album id now based on the current microtime (#27) +- `Improved` Back-end modules and plugins +- `Improved` Database connect function and update mechanism +- `Improved` Default photo title now "Untitled" +- `Improved` Move to next photo after after moving a picture (#437) +- `Improved` Return to album overview when canceling album password input +- `Improved` URL import now accepts photo URLs containing "?" and ":" (Thanks @qligier, #482) +- `Improved` Replaced date by strftime to simplify date translations (Thanks @qligier, #461) +- `Fixed` Missing icons in Safari 9.1 +- `Fixed` duplicate uploads (Thanks @qligier, #433) +- `Fixed` incorrect escaping when using backslashes +- `Fixed` session_start() after sending headers (#433) +- `Fixed` error when deleting last open photo in album +- `Fixed` Photo sometimes not loading when visiting directly +- `Fixed` Move album, merge album and switch album/photo menus no longer show empty titles for untitled albums/photos + ## v3.0.9 Released January 10, 2016 diff --git a/docs/FAQ.md b/docs/FAQ.md index d0874dd..5c1dadb 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,5 +1,5 @@ #### Lychee is not working -If Lychee is not working properly, try to open `plugins/check/index.php`. This script will display all errors it can find. +If Lychee is not working properly, try to open `plugins/Diagnostics/index.php`. This script will display all errors it can find. #### What do I need to run Lychee on my server? To run Lychee, everything you need is a web-server with PHP 5.3 or later and a MySQL-Database. @@ -38,7 +38,7 @@ To backup your Lychee installation you need to do the following steps: - INSERT INTO lychee_photos_backup SELECT * FROM lychee_photos; - CREATE TABLE lychee_settings_backup LIKE lychee_settings; - INSERT INTO lychee_settings_backup SELECT * FROM lychee_settings; - + #### Can I use my existing folder-structure? No. Lychee has it's own folder-structure and database. Please upload or import all your photos to use them. @@ -53,3 +53,6 @@ Yes. Lychee uses ImageMagick when available. #### Blank screen when viewing a photo using iOS There's a problem with images compressed by ImageOptim. [Read more.](https://github.com/electerious/Lychee/issues/175#issuecomment-47403992) + +#### How to change the title of the site? +[#455](https://github.com/electerious/Lychee/issues/455) \ No newline at end of file diff --git a/docs/Plugins.md b/docs/Plugins.md index deef245..046c2f4 100644 --- a/docs/Plugins.md +++ b/docs/Plugins.md @@ -11,50 +11,47 @@ The plugin-system of Lychee allows you to execute scripts, when a certain action ### How to create a plugin -1. Create a folder in `plugins/` -2. Create an `index.php` within the new folder and with the following content: +1. Create a folder in `plugins/` (e.g. `plugins/ExamplePlugin/`) +2. Create an `ExamplePlugin.php` file within the new folder and add the following content: ```php database = $database; - $this->settings = $settings; - - # Add more code here if wanted - # __construct() will be called every time Lychee gets called - # Make sure this part is performant + /** + * Add code here if wanted + * __construct() will be called every time Lychee gets called + * Make sure this part is performant + */ return true; } - public function update(\SplSubject $subject) { + public function update(SplSubject $subject) { + + /** + * Check if the called hook is the hook you are waiting for + * A list of all hooks is available online + */ - # Check if the called hook is the hook you are waiting for - # A list of all hooks is available online if ($subject->action!=='Photo::add:before') return false; - # Do something when Photo::add:before gets called - # $this->database => The database of Lychee - # $this->settings => The settings of Lychee - # $subject->args => Params passed to the original function + /** + * Do something when Photo::add:before gets called + * Database::get() => Database connection of Lychee + * Settings::get() => Settings of Lychee + * $subject->action => Called hook + * $subject->args => Params passed to the original function + */ return true; @@ -62,15 +59,14 @@ class ExamplePlugin implements SplObserver { } -# Register your plugin -$plugins->attach(new ExamplePlugin($database, $settings)); +?> ``` -3. Add the plugin-path to the database of Lychee +3. Add the class name to the database of Lychee -Select the table `lychee_settings` and edit the value of `plugins` to the path of your plugin. The path must be relative from the `plugins/`-folder: `ExamplePlugin/index.php`. +Select the table `lychee_settings` and add the [external fully qualified name](http://php.net/manual/en/language.namespaces.importing.php) to the value of `plugins` (e.g. `ExamplePlugin\ExamplePlugin`). Please ensure that the folder has the same name as the namespace and the file the same name as the class. -Divide multiple plugins with semicolons: `Plugin01/index.php;Plugin02/index.php`. +Divide multiple plugins with semicolons: `ExamplePlugin\ExamplePlugin;ExampleTwoPlugin\ExampleTwoPlugin`. ### Available hooks diff --git a/docs/Settings.md b/docs/Settings.md index b91c0f4..c762708 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -22,12 +22,6 @@ All settings are stored in the database. You can change the properties manually, Your photos and albums are protected by a username and password. If both rows are empty, Lychee will prompt you to set them. -#### Thumb Quality - - thumbQuality = [0-100] - -Less means an inferiority quality of your thumbs, but faster loading. More means a better quality of your thumbs, but slower loading. The default value is 90. The allowed values are between 0 and 100. - #### Check For Updates checkForUpdates = [0|1] @@ -58,12 +52,6 @@ This key is required to use the Dropbox import feature from your server. Lychee If `1`, Lychee will use Imagick when available. Disable [Imagick](http://www.imagemagick.org) if you have problems or if you are using an outdated version. Lychee will use [GD](http://php.net/manual/en/book.image.php) when Imagick is disabled or not available. -#### Medium - - medium = [0|1] - -If `1`, Lychee will create a second, smaller version of your photo. This feature requires [Imagick](http://www.imagemagick.org) on your server and an activated `imagick` option the the settings table. - #### Skip Duplicates on Upload skipDuplicates = [0|1] diff --git a/docs/Update.md b/docs/Update.md index 4fbec1d..ad8529c 100644 --- a/docs/Update.md +++ b/docs/Update.md @@ -1,15 +1,20 @@ +### Update requirements + +- Your system must comply with the latest [system requirements](https://github.com/electerious/Lychee/blob/master/docs/Installation.md) of Lychee. +- Ensure that your current version is greater than 2.7.0. Updates from older Lychee versions aren't supported. + ### Update with `git` -The easiest way to update Lychee is with `git`: +Updating Lychee with `git` is the easiest way: git pull - + ### Update manually -1. Download the [newest Version](https://github.com/electerious/Lychee/archive/master.zip) +1. Download the [newest Version](https://github.com/electerious/Lychee/release) 2. Replace all existing files, excluding `uploads/` and `data/` 3. Open Lychee (and enter your database details) ### Changelog -Take a look at the [Changelog](Changelog.md) to see what's new. +Take a look at the [Changelog](Changelog.md) to see what's new. \ No newline at end of file diff --git a/php/Access/Access.php b/php/Access/Access.php new file mode 100644 index 0000000..43b8bab --- /dev/null +++ b/php/Access/Access.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/php/Access/Admin.php b/php/Access/Admin.php new file mode 100644 index 0000000..c706653 --- /dev/null +++ b/php/Access/Admin.php @@ -0,0 +1,344 @@ +get(false)); + + } + + // Album functions + + private static function getAlbumAction() { + + Validator::required(isset($_POST['albumID']), __METHOD__); + + $album = new Album($_POST['albumID']); + Response::json($album->get()); + + } + + private static function addAlbumAction() { + + Validator::required(isset($_POST['title']), __METHOD__); + + $album = new Album(null); + Response::json($album->add($_POST['title']), JSON_NUMERIC_CHECK); + + } + + private static function setAlbumTitleAction() { + + Validator::required(isset($_POST['albumIDs'], $_POST['title']), __METHOD__); + + $album = new Album($_POST['albumIDs']); + Response::json($album->setTitle($_POST['title'])); + + } + + private static function setAlbumDescriptionAction() { + + Validator::required(isset($_POST['albumID'], $_POST['description']), __METHOD__); + + $album = new Album($_POST['albumID']); + Response::json($album->setDescription($_POST['description'])); + + } + + private static function setAlbumPublicAction() { + + Validator::required(isset($_POST['albumID'], $_POST['password'], $_POST['visible'], $_POST['downloadable']), __METHOD__); + + $album = new Album($_POST['albumID']); + Response::json($album->setPublic($_POST['public'], $_POST['password'], $_POST['visible'], $_POST['downloadable'])); + + } + + private static function deleteAlbumAction() { + + Validator::required(isset($_POST['albumIDs']), __METHOD__); + + $album = new Album($_POST['albumIDs']); + Response::json($album->delete()); + + } + + private static function mergeAlbumsAction() { + + Validator::required(isset($_POST['albumIDs']), __METHOD__); + $album = new Album($_POST['albumIDs']); + Response::json($album->merge()); + + } + + // Photo functions + + private static function getPhotoAction() { + + Validator::required(isset($_POST['photoID'], $_POST['albumID']), __METHOD__); + + $photo = new Photo($_POST['photoID']); + Response::json($photo->get($_POST['albumID'])); + + } + + private static function setPhotoTitleAction() { + + Validator::required(isset($_POST['photoIDs'], $_POST['title']), __METHOD__); + + $photo = new Photo($_POST['photoIDs']); + Response::json($photo->setTitle($_POST['title'])); + + } + + private static function setPhotoDescriptionAction() { + + Validator::required(isset($_POST['photoID'], $_POST['description']), __METHOD__); + + $photo = new Photo($_POST['photoID']); + Response::json($photo->setDescription($_POST['description'])); + + } + + private static function setPhotoStarAction() { + + Validator::required(isset($_POST['photoIDs']), __METHOD__); + + $photo = new Photo($_POST['photoIDs']); + Response::json($photo->setStar()); + + } + + private static function setPhotoPublicAction() { + + Validator::required(isset($_POST['photoID']), __METHOD__); + + $photo = new Photo($_POST['photoID']); + Response::json($photo->setPublic()); + + } + + private static function setPhotoAlbumAction() { + + Validator::required(isset($_POST['photoIDs'], $_POST['albumID']), __METHOD__); + + $photo = new Photo($_POST['photoIDs']); + Response::json($photo->setAlbum($_POST['albumID'])); + + } + + private static function setPhotoTagsAction() { + + Validator::required(isset($_POST['photoIDs'], $_POST['tags']), __METHOD__); + + $photo = new Photo($_POST['photoIDs']); + Response::json($photo->setTags($_POST['tags'])); + + } + + private static function duplicatePhotoAction() { + + Validator::required(isset($_POST['photoIDs']), __METHOD__); + + $photo = new Photo($_POST['photoIDs']); + Response::json($photo->duplicate()); + + } + + private static function deletePhotoAction() { + + Validator::required(isset($_POST['photoIDs']), __METHOD__); + + $photo = new Photo($_POST['photoIDs']); + Response::json($photo->delete()); + + } + + // Add functions + + private static function uploadAction() { + + Validator::required(isset($_FILES, $_POST['albumID']), __METHOD__); + + $photo = new Photo(null); + Response::json($photo->add($_FILES, $_POST['albumID']), JSON_NUMERIC_CHECK); + + } + + private static function importUrlAction() { + + Validator::required(isset($_POST['url'], $_POST['albumID']), __METHOD__); + + $import = new Import(); + Response::json($import->url($_POST['url'], $_POST['albumID'])); + + } + + private static function importServerAction() { + + Validator::required(isset($_POST['albumID'], $_POST['path']), __METHOD__); + + $import = new Import(); + Response::json($import->server($_POST['path'], $_POST['albumID'])); + + } + + // Search functions + + private static function searchAction() { + + Validator::required(isset($_POST['term']), __METHOD__); + + Response::json(search($_POST['term'])); + + } + + // Session functions + + private static function initAction() { + + $session = new Session(); + Response::json($session->init(false)); + + } + + private static function loginAction() { + + Validator::required(isset($_POST['user'], $_POST['password']), __METHOD__); + + $session = new Session(); + Response::json($session->login($_POST['user'], $_POST['password'])); + + } + + private static function logoutAction() { + + $session = new Session(); + Response::json($session->logout()); + + } + + // Settings functions + + private static function setLoginAction() { + + Validator::required(isset($_POST['username'], $_POST['password']), __METHOD__); + + if (isset($_POST['oldPassword'])===false) $_POST['oldPassword'] = ''; + Response::json(Settings::setLogin($_POST['oldPassword'], $_POST['username'], $_POST['password'])); + + } + + private static function setSortingAction() { + + Validator::required(isset($_POST['typeAlbums'], $_POST['orderAlbums'], $_POST['typePhotos'], $_POST['orderPhotos']), __METHOD__); + + $sA = Settings::setSortingAlbums($_POST['typeAlbums'], $_POST['orderAlbums']); + $sP = Settings::setSortingPhotos($_POST['typePhotos'], $_POST['orderPhotos']); + + if ($sA===true&&$sP===true) Response::json(true); + else Response::json(false); + + } + + private static function setDropboxKeyAction() { + + Validator::required(isset($_POST['key']), __METHOD__); + + Response::json(Settings::setDropboxKey($_POST['key'])); + + } + + // Get functions + + private static function getAlbumArchiveAction() { + + Validator::required(isset($_GET['albumID']), __METHOD__); + + $album = new Album($_GET['albumID']); + $album->getArchive(); + + } + + private static function getPhotoArchiveAction() { + + Validator::required(isset($_GET['photoID']), __METHOD__); + + $photo = new Photo($_GET['photoID']); + $photo->getArchive(); + + } + +} + +?> \ No newline at end of file diff --git a/php/Access/Guest.php b/php/Access/Guest.php new file mode 100644 index 0000000..84e3f69 --- /dev/null +++ b/php/Access/Guest.php @@ -0,0 +1,185 @@ +get(true)); + + } + + // Album functions + + private static function getAlbumAction() { + + Validator::required(isset($_POST['albumID'], $_POST['password']), __METHOD__); + + $album = new Album($_POST['albumID']); + + if ($album->getPublic()===true) { + + // Album public + if ($album->checkPassword($_POST['password'])===true) Response::json($album->get()); + else Response::warning('Wrong password!'); + + } else { + + // Album private + Response::warning('Album private!'); + + } + + } + + private static function checkAlbumAccessAction() { + + Validator::required(isset($_POST['albumID'], $_POST['password']), __METHOD__); + + $album = new Album($_POST['albumID']); + + if ($album->getPublic()===true) { + + // Album public + if ($album->checkPassword($_POST['password'])===true) Response::json(true); + else Response::json(false); + + } else { + + // Album private + Response::json(false); + + } + + } + + // Photo functions + + private static function getPhotoAction() { + + Validator::required(isset($_POST['photoID'], $_POST['albumID'], $_POST['password']), __METHOD__); + + $photo = new Photo($_POST['photoID']); + + $pgP = $photo->getPublic($_POST['password']); + + if ($pgP===2) Response::json($photo->get($_POST['albumID'])); + else if ($pgP===1) Response::warning('Wrong password!'); + else if ($pgP===0) Response::warning('Photo private!'); + + } + + // Session functions + + private static function initAction() { + + $session = new Session(); + Response::json($session->init(true)); + + } + + private static function loginAction() { + + Validator::required(isset($_POST['user'], $_POST['password']), __METHOD__); + + $session = new Session(); + Response::json($session->login($_POST['user'], $_POST['password'])); + + } + + private static function logoutAction() { + + $session = new Session(); + Response::json($session->logout()); + + } + + // $_GET functions + + private static function getAlbumArchiveAction() { + + Validator::required(isset($_GET['albumID'], $_GET['password']), __METHOD__); + + $album = new Album($_GET['albumID']); + + if ($album->getPublic()&&$album->getDownloadable()) { + + // Album Public + if ($album->checkPassword($_GET['password'])) $album->getArchive(); + else Response::warning('Wrong password!'); + + } else { + + // Album Private + Response::warning('Album private or not downloadable!'); + + } + + } + + private static function getPhotoArchiveAction() { + + Validator::required(isset($_GET['photoID'], $_GET['password']), __METHOD__); + + $photo = new Photo($_GET['photoID']); + + $pgP = $photo->getPublic($_GET['password']); + + // Photo Download + if ($pgP===2) { + + // Photo Public + $photo->getArchive(); + + } else { + + // Photo Private + Response::warning('Photo private or password incorrect!'); + + } + + } + +} + +?> \ No newline at end of file diff --git a/php/Access/Installation.php b/php/Access/Installation.php new file mode 100644 index 0000000..4911904 --- /dev/null +++ b/php/Access/Installation.php @@ -0,0 +1,45 @@ + LYCHEE_STATUS_NOCONFIG + ); + + Response::json($return); + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Album.php b/php/Modules/Album.php new file mode 100644 index 0000000..1c03ba5 --- /dev/null +++ b/php/Modules/Album.php @@ -0,0 +1,647 @@ +albumIDs = $albumIDs; + + return true; + + } + + /** + * @return string|false ID of the created album. + */ + public function add($title = 'Untitled') { + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Properties + $id = generateID(); + $sysstamp = time(); + $public = 0; + $visible = 1; + + // Database + $query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, sysstamp, public, visible) VALUES ('?', '?', '?', '?', '?')", array(LYCHEE_TABLE_ALBUMS, $id, $title, $sysstamp, $public, $visible)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return $id; + + } + + /** + * Rurns album-attributes into a front-end friendly format. Note that some attributes remain unchanged. + * @return array Returns album-attributes in a normalized structure. + */ + public static function prepareData(array $data) { + + // This function requires the following album-attributes and turns them + // into a front-end friendly format: id, title, public, sysstamp, password + // Note that some attributes remain unchanged + + // Init + $album = null; + + // Set unchanged attributes + $album['id'] = $data['id']; + $album['title'] = $data['title']; + $album['public'] = $data['public']; + + // Additional attributes + // Only part of $album when available + if (isset($data['description'])) $album['description'] = $data['description']; + if (isset($data['visible'])) $album['visible'] = $data['visible']; + if (isset($data['downloadable'])) $album['downloadable'] = $data['downloadable']; + + // Parse date + $album['sysdate'] = strftime('%B %Y', $data['sysstamp']); + + // Parse password + $album['password'] = ($data['password']=='' ? '0' : '1'); + + // Parse thumbs or set default value + $album['thumbs'] = (isset($data['thumbs']) ? explode(',', $data['thumbs']) : array()); + + return $album; + + } + + /** + * @return array|false Returns an array of photos and album information or false on failure. + */ + public function get() { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Get album information + switch ($this->albumIDs) { + + case 'f': + $return['public'] = '0'; + $query = Database::prepare(Database::get(), "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE star = 1 " . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + break; + + case 's': + $return['public'] = '0'; + $query = Database::prepare(Database::get(), "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE public = 1 " . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + break; + + case 'r': + $return['public'] = '0'; + $query = Database::prepare(Database::get(), "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) " . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + break; + + case '0': + $return['public'] = '0'; + $query = Database::prepare(Database::get(), "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = 0 " . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + break; + + default: + $query = Database::prepare(Database::get(), "SELECT * FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + $return = $albums->fetch_assoc(); + $return = Album::prepareData($return); + $query = Database::prepare(Database::get(), "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = '?' " . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); + break; + + } + + // Get photos + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + $previousPhotoID = ''; + + if ($photos===false) return false; + + while ($photo = $photos->fetch_assoc()) { + + // Turn data from the database into a front-end friendly format + $photo = Photo::prepareData($photo); + + // Set previous and next photoID for navigation purposes + $photo['previousPhoto'] = $previousPhotoID; + $photo['nextPhoto'] = ''; + + // Set current photoID as nextPhoto of previous photo + if ($previousPhotoID!=='') $return['content'][$previousPhotoID]['nextPhoto'] = $photo['id']; + $previousPhotoID = $photo['id']; + + // Add to return + $return['content'][$photo['id']] = $photo; + + } + + if ($photos->num_rows===0) { + + // Album empty + $return['content'] = false; + + } else { + + // Enable next and previous for the first and last photo + $lastElement = end($return['content']); + $lastElementId = $lastElement['id']; + $firstElement = reset($return['content']); + $firstElementId = $firstElement['id']; + + if ($lastElementId!==$firstElementId) { + $return['content'][$lastElementId]['nextPhoto'] = $firstElementId; + $return['content'][$firstElementId]['previousPhoto'] = $lastElementId; + } + + } + + $return['id'] = $this->albumIDs; + $return['num'] = $photos->num_rows; + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return $return; + + } + + /** + * Starts a download of an album. + * @return resource|boolean Sends a ZIP-file or returns false on failure. + */ + public function getArchive() { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Illicit chars + $badChars = array_merge( + array_map('chr', range(0,31)), + array("<", ">", ":", '"', "/", "\\", "|", "?", "*") + ); + + // Photos query + switch($this->albumIDs) { + case 's': + $photos = Database::prepare(Database::get(), 'SELECT title, url FROM ? WHERE public = 1', array(LYCHEE_TABLE_PHOTOS)); + $zipTitle = 'Public'; + break; + case 'f': + $photos = Database::prepare(Database::get(), 'SELECT title, url FROM ? WHERE star = 1', array(LYCHEE_TABLE_PHOTOS)); + $zipTitle = 'Starred'; + break; + case 'r': + $photos = Database::prepare(Database::get(), 'SELECT title, url FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) GROUP BY checksum', array(LYCHEE_TABLE_PHOTOS)); + $zipTitle = 'Recent'; + break; + default: + $photos = Database::prepare(Database::get(), "SELECT title, url FROM ? WHERE album = '?'", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); + $zipTitle = 'Unsorted'; + } + + // Get title from database when album is not a SmartAlbum + if ($this->albumIDs!=0&&is_numeric($this->albumIDs)) { + + $query = Database::prepare(Database::get(), "SELECT title FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + $album = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($album===false) return false; + + // Get album object + $album = $album->fetch_object(); + + // Album not found? + if ($album===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album'); + return false; + } + + // Set title + $zipTitle = $album->title; + + } + + // Escape title + $zipTitle = str_replace($badChars, '', $zipTitle); + + $filename = LYCHEE_DATA . $zipTitle . '.zip'; + + // Create zip + $zip = new ZipArchive(); + if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not create ZipArchive'); + return false; + } + + // Execute query + $photos = Database::execute(Database::get(), $photos, __METHOD__, __LINE__); + + if ($album===null) return false; + + // Check if album empty + if ($photos->num_rows==0) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not create ZipArchive without images'); + return false; + } + + // Parse each path + $files = array(); + while ($photo = $photos->fetch_object()) { + + // Parse url + $photo->url = LYCHEE_UPLOADS_BIG . $photo->url; + + // Parse title + $photo->title = str_replace($badChars, '', $photo->title); + if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled'; + + // Check if readable + if (!@is_readable($photo->url)) continue; + + // Get extension of image + $extension = getExtension($photo->url, false); + + // Set title for photo + $zipFileName = $zipTitle . '/' . $photo->title . $extension; + + // Check for duplicates + if (!empty($files)) { + $i = 1; + while (in_array($zipFileName, $files)) { + + // Set new title for photo + $zipFileName = $zipTitle . '/' . $photo->title . '-' . $i . $extension; + + $i++; + + } + } + + // Add to array + $files[] = $zipFileName; + + // Add photo to zip + $zip->addFile($photo->url, $zipFileName); + + } + + // Finish zip + $zip->close(); + + // Send zip + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=\"$zipTitle.zip\""); + header("Content-Length: " . filesize($filename)); + readfile($filename); + + // Delete zip + unlink($filename); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + public function setTitle($title = 'Untitled') { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Execute query + $query = Database::prepare(Database::get(), "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $title, $this->albumIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + public function setDescription($description = '') { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Execute query + $query = Database::prepare(Database::get(), "UPDATE ? SET description = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $description, $this->albumIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * @return boolean Returns true when the album is public. + */ + public function getPublic() { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + if ($this->albumIDs==='0'||$this->albumIDs==='s'||$this->albumIDs==='f') return false; + + // Execute query + $query = Database::prepare(Database::get(), "SELECT public FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($albums===false) return false; + + // Get album object + $album = $albums->fetch_object(); + + // Album not found? + if ($album===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album'); + return false; + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($album->public==1) return true; + return false; + + } + + /** + * @return boolean Returns true when the album is downloadable. + */ + public function getDownloadable() { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + if ($this->albumIDs==='0'||$this->albumIDs==='s'||$this->albumIDs==='f'||$this->albumIDs==='r') return false; + + // Execute query + $query = Database::prepare(Database::get(), "SELECT downloadable FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($albums===false) return false; + + // Get album object + $album = $albums->fetch_object(); + + // Album not found? + if ($album===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album'); + return false; + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($album->downloadable==1) return true; + return false; + + } + + /** + * @return boolean Returns true when successful. + */ + public function setPublic($public, $password, $visible, $downloadable) { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Convert values + $public = ($public==='1' ? 1 : 0); + $visible = ($visible==='1' ? 1 : 0); + $downloadable = ($downloadable==='1' ? 1 : 0); + + // Set public + $query = Database::prepare(Database::get(), "UPDATE ? SET public = '?', visible = '?', downloadable = '?', password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $public, $visible, $downloadable, $this->albumIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + // Reset permissions for photos + if ($public===1) { + + $query = Database::prepare(Database::get(), "UPDATE ? SET public = 0 WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + // Set password + if (isset($password)&&strlen($password)>0) return $this->setPassword($password); + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + private function setPassword($password) { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + if (strlen($password)>0) { + + // Get hashed password + $password = getHashedString($password); + + // Set hashed password + // Do not prepare $password because it is hashed and save + // Preparing (escaping) the password would destroy the hash + $query = Database::prepare(Database::get(), "UPDATE ? SET password = '$password' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + + } else { + + // Unset password + $query = Database::prepare(Database::get(), "UPDATE ? SET password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + + } + + // Execute query + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * @return boolean Returns when album is public. + */ + public function checkPassword($password) { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Execute query + $query = Database::prepare(Database::get(), "SELECT password FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($albums===false) return false; + + // Get album object + $album = $albums->fetch_object(); + + // Album not found? + if ($album===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album'); + return false; + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + // Check if password is correct + if ($album->password=='') return true; + if ($album->password===crypt($password, $album->password)) return true; + return false; + + } + + /** + * @return boolean Returns true when successful. + */ + public function merge() { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Convert to array + $albumIDs = explode(',', $this->albumIDs); + + // Get first albumID + $albumID = array_splice($albumIDs, 0, 1); + $albumID = $albumID[0]; + + $query = Database::prepare(Database::get(), "UPDATE ? SET album = ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->albumIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + // $albumIDs contains all IDs without the first albumID + // Convert to string + $filteredIDs = implode(',', $albumIDs); + + $query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $filteredIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + public function delete() { + + // Check dependencies + Validator::required(isset($this->albumIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Init vars + $photoIDs = array(); + + // Execute query + $query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // Only delete photos when albums contain photos + if ($photos->num_rows>0) { + + // Add each id to photoIDs + while ($row = $photos->fetch_object()) $photoIDs[] = $row->id; + + // Convert photoIDs to a string + $photoIDs = implode(',', $photoIDs); + + // Delete all photos + $photo = new Photo($photoIDs); + if ($photo->delete()!==true) return false; + + } + + // Delete albums + $query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Albums.php b/php/Modules/Albums.php new file mode 100644 index 0000000..e57ee84 --- /dev/null +++ b/php/Modules/Albums.php @@ -0,0 +1,191 @@ +activate(__METHOD__, 0, func_get_args()); + + // Initialize return var + $return = array( + 'smartalbums' => null, + 'albums' => null, + 'num' => 0 + ); + + // Get SmartAlbums + if ($public===false) $return['smartalbums'] = $this->getSmartAlbums(); + + // Albums query + if ($public===false) $query = Database::prepare(Database::get(), 'SELECT id, title, public, sysstamp, password FROM ? ' . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS)); + else $query = Database::prepare(Database::get(), 'SELECT id, title, public, sysstamp, password FROM ? WHERE public = 1 AND visible <> 0 ' . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS)); + + // Execute query + $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($albums===false) return false; + + // For each album + while ($album = $albums->fetch_assoc()) { + + // Turn data from the database into a front-end friendly format + $album = Album::prepareData($album); + + // Thumbs + if (($public===true&&$album['password']==='0')|| + ($public===false)) { + + // Execute query + $query = Database::prepare(Database::get(), "SELECT thumbUrl FROM ? WHERE album = '?' ORDER BY star DESC, " . substr(Settings::get()['sortingPhotos'], 9) . " LIMIT 3", array(LYCHEE_TABLE_PHOTOS, $album['id'])); + $thumbs = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($thumbs===false) return false; + + // For each thumb + $k = 0; + while ($thumb = $thumbs->fetch_object()) { + $album['thumbs'][$k] = LYCHEE_URL_UPLOADS_THUMB . $thumb->thumbUrl; + $k++; + } + + } + + // Add to return + $return['albums'][] = $album; + + } + + // Num of albums + $return['num'] = $albums->num_rows; + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return $return; + + } + + /** + * @return array|false Returns an array of smart albums or false on failure. + */ + private function getSmartAlbums() { + + // Initialize return var + $return = array( + 'unsorted' => null, + 'public' => null, + 'starred' => null, + 'recent' => null + ); + + /** + * Unsorted + */ + + $query = Database::prepare(Database::get(), 'SELECT thumbUrl FROM ? WHERE album = 0 ' . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + $unsorted = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + $i = 0; + + if ($unsorted===false) return false; + + $return['unsorted'] = array( + 'thumbs' => array(), + 'num' => $unsorted->num_rows + ); + + while($row = $unsorted->fetch_object()) { + if ($i<3) { + $return['unsorted']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row->thumbUrl; + $i++; + } else break; + } + + /** + * Starred + */ + + $query = Database::prepare(Database::get(), 'SELECT thumbUrl FROM ? WHERE star = 1 ' . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + $starred = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + $i = 0; + + if ($starred===false) return false; + + $return['starred'] = array( + 'thumbs' => array(), + 'num' => $starred->num_rows + ); + + while($row3 = $starred->fetch_object()) { + if ($i<3) { + $return['starred']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row3->thumbUrl; + $i++; + } else break; + } + + /** + * Public + */ + + $query = Database::prepare(Database::get(), 'SELECT thumbUrl FROM ? WHERE public = 1 ' . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + $public = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + $i = 0; + + if ($public===false) return false; + + $return['public'] = array( + 'thumbs' => array(), + 'num' => $public->num_rows + ); + + while($row2 = $public->fetch_object()) { + if ($i<3) { + $return['public']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row2->thumbUrl; + $i++; + } else break; + } + + /** + * Recent + */ + + $query = Database::prepare(Database::get(), 'SELECT thumbUrl FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) ' . Settings::get()['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); + $recent = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + $i = 0; + + if ($recent===false) return false; + + $return['recent'] = array( + 'thumbs' => array(), + 'num' => $recent->num_rows + ); + + while($row3 = $recent->fetch_object()) { + if ($i<3) { + $return['recent']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row3->thumbUrl; + $i++; + } else break; + } + + // Return SmartAlbums + return $return; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Config.php b/php/Modules/Config.php new file mode 100644 index 0000000..892e03d --- /dev/null +++ b/php/Modules/Config.php @@ -0,0 +1,79 @@ +"; + + // Save file + if (@file_put_contents(LYCHEE_CONFIG_FILE, $config)===false) return 'Warning: Could not create file!'; + + return true; + + } + + /** + * @return boolean Returns true when the config exists. + */ + public static function exists() { + + return file_exists(LYCHEE_CONFIG_FILE); + + } + + /** + * @return array Returns the config. + */ + public static function get() { + + require(LYCHEE_CONFIG_FILE); + + return(array( + 'host' => $dbHost, + 'user' => $dbUser, + 'password' => $dbPassword, + 'name' => $dbName, + 'prefix' => $dbTablePrefix + )); + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Database.php b/php/Modules/Database.php new file mode 100755 index 0000000..2df65dc --- /dev/null +++ b/php/Modules/Database.php @@ -0,0 +1,411 @@ +connection; + + } + + /** + * Exits on error. + * @return boolean Returns true when successful. + */ + private function __construct($host, $user, $password, $name = 'lychee', $dbTablePrefix) { + + // Check dependencies + Validator::required(isset($host, $user, $password, $name), __METHOD__); + + // Define the table prefix + defineTablePrefix($dbTablePrefix); + + // Open a new connection to the MySQL server + $connection = self::connect($host, $user, $password); + + // Check if the connection was successful + if ($connection===false) Response::error($connection->connect_error); + + if (self::setCharset($connection)===false) Response::error('Could not set database charset!'); + + // Create database + if (self::createDatabase($connection, $name)===false) Response::error('Could not create database!'); + + // Create tables + if (self::createTables($connection)===false) Response::error('Could not create tables!'); + + // Update database + if (self::update($connection, $name)===false) Response::error('Could not update database and tables!'); + + $this->connection = $connection; + + return true; + + } + + /** + * @return object|false Returns the connection when successful. + */ + public static function connect($host = 'localhost', $user, $password) { + + // Open a new connection to the MySQL server + $connection = @new Mysqli($host, $user, $password); + + // Check if the connection was successful + if ($connection->connect_errno) return false; + + return $connection; + + } + + /** + * @return boolean Returns true when successful. + */ + private static function setCharset($connection) { + + // Check dependencies + Validator::required(isset($connection), __METHOD__); + + // Avoid sql injection on older MySQL versions by using GBK + if ($connection->server_version<50500) @$connection->set_charset('GBK'); + else @$connection->set_charset('utf8'); + + // Set unicode + $query = 'SET NAMES utf8'; + $result = self::execute($connection, $query, null, null); + + if ($result===false) return false; + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + public static function createDatabase($connection, $name = 'lychee') { + + // Check dependencies + Validator::required(isset($connection), __METHOD__); + + // Check if database exists + if ($connection->select_db($name)===true) return true; + + // Create database + $query = self::prepare($connection, 'CREATE DATABASE IF NOT EXISTS ?', array($name)); + $result = self::execute($connection, $query, null, null); + + if ($result===false) return false; + if ($connection->select_db($name)===false) return false; + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + private static function createTables($connection) { + + // Check dependencies + Validator::required(isset($connection), __METHOD__); + + // Check if tables exist + $query = self::prepare($connection, 'SELECT * FROM ?, ?, ?, ? LIMIT 0', array(LYCHEE_TABLE_PHOTOS, LYCHEE_TABLE_ALBUMS, LYCHEE_TABLE_SETTINGS, LYCHEE_TABLE_LOG)); + $result = self::execute($connection, $query, null, null); + if ($result!==false) return true; + + // Check if log table exists + $exist = self::prepare($connection, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_LOG)); + $result = self::execute($connection, $exist, null, null); + + if ($result===false) { + + // Read file + $file = __DIR__ . '/../database/log_table.sql'; + $query = @file_get_contents($file); + + if ($query===false) return false; + + // Create table + $query = self::prepare($connection, $query, array(LYCHEE_TABLE_LOG)); + $result = self::execute($connection, $query, null, null); + + if ($result===false) return false; + + } + + // Check if settings table exists + $exist = self::prepare($connection, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_SETTINGS)); + $result = self::execute($connection, $exist, __METHOD__, __LINE__); + + if ($result===false) { + + // Read file + $file = __DIR__ . '/../database/settings_table.sql'; + $query = @file_get_contents($file); + + if ($query===false) { + Log::error($connection, __METHOD__, __LINE__, 'Could not load query for lychee_settings'); + return false; + } + + // Create table + $query = self::prepare($connection, $query, array(LYCHEE_TABLE_SETTINGS)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + // Read file + $file = __DIR__ . '/../database/settings_content.sql'; + $query = @file_get_contents($file); + + if ($query===false) { + Log::error($connection, __METHOD__, __LINE__, 'Could not load content-query for lychee_settings'); + return false; + } + + // Add content + $query = self::prepare($connection, $query, array(LYCHEE_TABLE_SETTINGS)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + // Generate identifier + $identifier = md5(microtime(true)); + $query = self::prepare($connection, "UPDATE `?` SET `value` = '?' WHERE `key` = 'identifier' LIMIT 1", array(LYCHEE_TABLE_SETTINGS, $identifier)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + } + + // Check if albums table exists + $exist = self::prepare($connection, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_ALBUMS)); + $result = self::execute($connection, $exist, __METHOD__, __LINE__); + + if ($result===false) { + + // Read file + $file = __DIR__ . '/../database/albums_table.sql'; + $query = @file_get_contents($file); + + if ($query===false) { + Log::error($connection, __METHOD__, __LINE__, 'Could not load query for lychee_albums'); + return false; + } + + // Create table + $query = self::prepare($connection, $query, array(LYCHEE_TABLE_ALBUMS)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + } + + // Check if photos table exists + $exist = self::prepare($connection, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_PHOTOS)); + $result = self::execute($connection, $exist, __METHOD__, __LINE__); + + if ($result===false) { + + // Read file + $file = __DIR__ . '/../database/photos_table.sql'; + $query = @file_get_contents($file); + + if ($query===false) { + Log::error($connection, __METHOD__, __LINE__, 'Could not load query for lychee_photos'); + return false; + } + + // Create table + $query = self::prepare($connection, $query, array(LYCHEE_TABLE_PHOTOS)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + } + + return true; + + } + + /** + * Exits when an update fails. + * @return boolean Returns true when successful. + */ + private static function update($connection, $dbName) { + + // Check dependencies + Validator::required(isset($connection, $dbName), __METHOD__); + + // Get current version + $query = self::prepare($connection, "SELECT * FROM ? WHERE `key` = 'version'", array(LYCHEE_TABLE_SETTINGS)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + // Extract current version + $current = $result->fetch_object()->value; + + // For each update + foreach (self::$versions as $version) { + + // Only update when newer version available + if ($version<=$current) continue; + + // Load update + include(__DIR__ . '/../database/update_' . $version . '.php'); + + } + + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + public static function setVersion($connection, $version) { + + // Check dependencies + Validator::required(isset($connection), __METHOD__); + + $query = self::prepare($connection, "UPDATE ? SET value = '?' WHERE `key` = 'version'", array(LYCHEE_TABLE_SETTINGS, $version)); + $result = self::execute($connection, $query, __METHOD__, __LINE__); + + if ($result===false) return false; + return true; + + } + + /** + * @return string Returns a escaped query. + */ + public static function prepare($connection, $query, array $data) { + + // Check dependencies + Validator::required(isset($connection, $query), __METHOD__); + + // Count the number of placeholders and compare it with the number of arguments + // If it doesn't match, calculate the difference and skip this number of placeholders before starting the replacement + // This avoids problems with placeholders in user-input + // $skip = Number of placeholders which need to be skipped + $skip = 0; + $temp = ''; + $num = array( + 'placeholder' => substr_count($query, '?'), + 'data' => count($data) + ); + + if (($num['data']-$num['placeholder'])<0) Log::notice($connection, __METHOD__, __LINE__, 'Could not completely prepare query. Query has more placeholders than values.'); + + foreach ($data as $value) { + + // Escape + $value = mysqli_real_escape_string($connection, $value); + + // Recalculate number of placeholders + $num['placeholder'] = substr_count($query, '?'); + + // Calculate number of skips + if ($num['placeholder']>$num['data']) $skip = $num['placeholder'] - $num['data']; + + if ($skip>0) { + + // Need to skip $skip placeholders, because the user input contained placeholders + // Calculate a substring which does not contain the user placeholders + // 1 or -1 is the length of the placeholder (placeholder = ?) + + $pos = -1; + for ($i=$skip; $i>0; $i--) $pos = strpos($query, '?', $pos + 1); + $pos++; + + $temp = substr($query, 0, $pos); // First part of $query + $query = substr($query, $pos); // Last part of $query + + } + + // Put a backslash in front of every character that is part of the regular + // expression syntax. Avoids a backreference when using preg_replace. + $value = preg_quote($value); + + // Replace + $query = preg_replace('/\?/', $value, $query, 1); + + if ($skip>0) { + + // Reassemble the parts of $query + $query = $temp . $query; + + } + + // Reset skip + $skip = 0; + + // Decrease number of data elements + $num['data']--; + + } + + return $query; + + } + + /** + * @return object|false Returns the results on success. + */ + public static function execute($connection, $query, $function, $line) { + + // Check dependencies + Validator::required(isset($connection, $query), __METHOD__); + + // Only activate logging when $function and $line is set + $logging = ($function===null||$line===null ? false : true); + + // Execute query + $result = $connection->query($query); + + // Check if execution failed + if ($result===false) { + if ($logging===true) Log::error($connection, $function, $line, $connection->error); + return false; + } + + return $result; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Import.php b/php/Modules/Import.php new file mode 100644 index 0000000..c0d1a32 --- /dev/null +++ b/php/Modules/Import.php @@ -0,0 +1,194 @@ +add will take care of it. + + $info = getimagesize($path); + $size = filesize($path); + $photo = new Photo(null); + + $nameFile = array(array()); + $nameFile[0]['name'] = $path; + $nameFile[0]['type'] = $info['mime']; + $nameFile[0]['tmp_name'] = $path; + $nameFile[0]['error'] = 0; + $nameFile[0]['size'] = $size; + $nameFile[0]['error'] = UPLOAD_ERR_OK; + + if ($photo->add($nameFile, $albumID, true)===false) return false; + return true; + + } + + /** + * @return boolean Returns true when successful. + */ + public function url($urls, $albumID = 0) { + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + $error = false; + + // Parse URLs + $urls = str_replace(' ', '%20', $urls); + $urls = explode(',', $urls); + + foreach ($urls as &$url) { + + // Validate photo type and extension even when $this->photo (=> $photo->add) will do the same. + // This prevents us from downloading invalid photos. + + // Verify extension + $extension = getExtension($url, true); + if (!in_array(strtolower($extension), Photo::$validExtensions, true)) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Photo format not supported (' . $url . ')'); + continue; + } + + // Verify image + $type = @exif_imagetype($url); + if (!in_array($type, Photo::$validTypes, true)) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Photo type not supported (' . $url . ')'); + continue; + } + + $filename = pathinfo($url, PATHINFO_FILENAME) . $extension; + $tmp_name = LYCHEE_DATA . $filename; + + if (@copy($url, $tmp_name)===false) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not copy file (' . $url . ') to temp-folder (' . $tmp_name . ')'); + continue; + } + + // Import photo + if (!$this->photo($tmp_name, $albumID)) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not import file (' . $tmp_name . ')'); + continue; + } + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($error===false) return true; + return false; + + } + + /** + * @return boolean|string Returns true when successful. + * Warning: Folder empty or no readable files to process! + * Notice: Import only contained albums! + */ + public function server($path, $albumID = 0) { + + // Parse path + if (!isset($path)) $path = LYCHEE_UPLOADS_IMPORT; + if (substr($path, -1)==='/') $path = substr($path, 0, -1); + + if (is_dir($path)===false) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Given path is not a directory (' . $path . ')'); + return false; + } + + // Skip folders of Lychee + if ($path===LYCHEE_UPLOADS_BIG||($path . '/')===LYCHEE_UPLOADS_BIG|| + $path===LYCHEE_UPLOADS_MEDIUM||($path . '/')===LYCHEE_UPLOADS_MEDIUM|| + $path===LYCHEE_UPLOADS_THUMB||($path . '/')===LYCHEE_UPLOADS_THUMB) { + Log::error(Database::get(), __METHOD__, __LINE__, 'The given path is a reserved path of Lychee (' . $path . ')'); + return false; + } + + $error = false; + $contains['photos'] = false; + $contains['albums'] = false; + + // Call plugins + // Note that updated albumId and path explicitly passed, rather + // than using func_get_args() which will only return original ones + Plugins::get()->activate(__METHOD__, 0, array($albumID, $path)); + + // Get all files + $files = glob($path . '/*'); + + foreach ($files as $file) { + + // It is possible to move a file because of directory permissions but + // the file may still be unreadable by the user + if (!is_readable($file)) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not read file or directory (' . $file . ')'); + continue; + } + + if (@exif_imagetype($file)!==false) { + + // Photo + + $contains['photos'] = true; + + if ($this->photo($file, $albumID)===false) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not import file (' . $file . ')'); + continue; + } + + } else if (is_dir($file)) { + + // Folder + + $album = new Album(null); + $newAlbumID = $album->add('[Import] ' . basename($file)); + $contains['albums'] = true; + + if ($newAlbumID===false) { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not create album in Lychee (' . $newAlbumID . ')'); + continue; + } + + $import = $this->server($file . '/', $newAlbumID); + + if ($import!==true&&$import!=='Notice: Import only contains albums!') { + $error = true; + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not import folder. Function returned warning.'); + continue; + } + + } + + } + + // Call plugins + // Note that updated albumId and path explicitly passed, rather + // than using func_get_args() which will only return original ones + Plugins::get()->activate(__METHOD__, 1, array($albumID, $path)); + + // The following returns will be caught in the front-end + if ($contains['photos']===false&&$contains['albums']===false) return 'Warning: Folder empty or no readable files to process!'; + if ($contains['photos']===false&&$contains['albums']===true) return 'Notice: Import only contained albums!'; + + if ($error===true) return false; + return true; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Log.php b/php/Modules/Log.php new file mode 100644 index 0000000..bd3fa65 --- /dev/null +++ b/php/Modules/Log.php @@ -0,0 +1,47 @@ + \ No newline at end of file diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php new file mode 100755 index 0000000..587cb8e --- /dev/null +++ b/php/Modules/Photo.php @@ -0,0 +1,1244 @@ +photoIDs = $photoIDs; + + return true; + + } + + /** + * Creats new photo(s). + * Exits on error. + * Use $returnOnError if you want to handle errors by your own. + * @return string|false ID of the added photo. + */ + public function add(array $files, $albumID = 0, $returnOnError = false) { + + // Check permissions + if (hasPermissions(LYCHEE_UPLOADS)===false|| + hasPermissions(LYCHEE_UPLOADS_BIG)===false|| + hasPermissions(LYCHEE_UPLOADS_THUMB)===false) { + Log::error(Database::get(), __METHOD__, __LINE__, 'An upload-folder is missing or not readable and writable'); + if ($returnOnError===true) return false; + Response::error('An upload-folder is missing or not readable and writable!'); + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + switch($albumID) { + + case 's': + // s for public (share) + $public = 1; + $star = 0; + $albumID = 0; + break; + + case 'f': + // f for starred (fav) + $star = 1; + $public = 0; + $albumID = 0; + break; + + case 'r': + // r for recent + $public = 0; + $star = 0; + $albumID = 0; + break; + + default: + $star = 0; + $public = 0; + break; + + } + + // Only process the first photo in the array + $file = $files[0]; + + // Check if file exceeds the upload_max_filesize directive + if ($file['error']===UPLOAD_ERR_INI_SIZE) { + Log::error(Database::get(), __METHOD__, __LINE__, 'The uploaded file exceeds the upload_max_filesize directive in php.ini'); + if ($returnOnError===true) return false; + Response::error('The uploaded file exceeds the upload_max_filesize directive in php.ini!'); + } + + // Check if file was only partially uploaded + if ($file['error']===UPLOAD_ERR_PARTIAL) { + Log::error(Database::get(), __METHOD__, __LINE__, 'The uploaded file was only partially uploaded'); + if ($returnOnError===true) return false; + Response::error('The uploaded file was only partially uploaded!'); + } + + // Check if writing file to disk failed + if ($file['error']===UPLOAD_ERR_CANT_WRITE) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Failed to write photo to disk'); + if ($returnOnError===true) return false; + Response::error('Failed to write photo to disk!'); + } + + // Check if a extension stopped the file upload + if ($file['error']===UPLOAD_ERR_EXTENSION) { + Log::error(Database::get(), __METHOD__, __LINE__, 'A PHP extension stopped the file upload'); + if ($returnOnError===true) return false; + Response::error('A PHP extension stopped the file upload!'); + } + + // Check if the upload was successful + if ($file['error']!==UPLOAD_ERR_OK) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Upload contains an error (' . $file['error'] . ')'); + if ($returnOnError===true) return false; + Response::error('Upload failed!'); + } + + // Verify extension + $extension = getExtension($file['name'], false); + if (!in_array(strtolower($extension), self::$validExtensions, true)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Photo format not supported'); + if ($returnOnError===true) return false; + Response::error('Photo format not supported!'); + } + + // Verify image + $type = @exif_imagetype($file['tmp_name']); + if (!in_array($type, self::$validTypes, true)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Photo type not supported'); + if ($returnOnError===true) return false; + Response::error('Photo type not supported!'); + } + + // Generate id + $id = generateID(); + + // Set paths + $tmp_name = $file['tmp_name']; + $photo_name = md5($id) . $extension; + $path = LYCHEE_UPLOADS_BIG . $photo_name; + + // Calculate checksum + $checksum = sha1_file($tmp_name); + if ($checksum===false) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not calculate checksum for photo'); + if ($returnOnError===true) return false; + Response::error('Could not calculate checksum for photo!'); + } + + // Check if image exists based on checksum + if ($checksum===false) { + + $checksum = ''; + $exists = false; + + } else { + + $exists = $this->exists($checksum); + + if ($exists!==false) { + $photo_name = $exists['photo_name']; + $path = $exists['path']; + $path_thumb = $exists['path_thumb']; + $medium = ($exists['medium']==='1' ? 1 : 0); + $exists = true; + } + + } + + if ($exists===false) { + + // Import if not uploaded via web + if (!is_uploaded_file($tmp_name)) { + if (!@copy($tmp_name, $path)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not copy photo to uploads'); + if ($returnOnError===true) return false; + Response::error('Could not copy photo to uploads!'); + } else @unlink($tmp_name); + } else { + if (!@move_uploaded_file($tmp_name, $path)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not move photo to uploads'); + if ($returnOnError===true) return false; + Response::error('Could not move photo to uploads!'); + } + } + + } else { + + // Photo already exists + // Check if the user wants to skip duplicates + if (Settings::get()['skipDuplicates']==='1') { + Log::notice(Database::get(), __METHOD__, __LINE__, 'Skipped upload of existing photo because skipDuplicates is activated'); + if ($returnOnError===true) return false; + Response::warning('This photo has been skipped because it\'s already in your library.'); + } + + } + + // Read infos + $info = $this->getInfo($path); + + // Use title of file if IPTC title missing + if ($info['title']==='') $info['title'] = substr(basename($file['name'], $extension), 0, 30); + + if ($exists===false) { + + // Set orientation based on EXIF data + if ($file['type']==='image/jpeg'&&isset($info['orientation'])&&$info['orientation']!=='') { + $adjustFile = $this->adjustFile($path, $info); + if ($adjustFile!==false) $info = $adjustFile; + else Log::notice(Database::get(), __METHOD__, __LINE__, 'Skipped adjustment of photo (' . $info['title'] . ')'); + } + + // Set original date + if ($info['takestamp']!==''&&$info['takestamp']!==0) @touch($path, $info['takestamp']); + + // Create Thumb + if (!$this->createThumb($path, $photo_name, $info['type'], $info['width'], $info['height'])) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not create thumbnail for photo'); + if ($returnOnError===true) return false; + Response::error('Could not create thumbnail for photo!'); + } + + // Create Medium + if ($this->createMedium($path, $photo_name, $info['width'], $info['height'])) $medium = 1; + else $medium = 0; + + // Set thumb url + $path_thumb = md5($id) . '.jpeg'; + + } + + // Save to DB + $values = array(LYCHEE_TABLE_PHOTOS, $id, $info['title'], $photo_name, $info['description'], '', $info['type'], $info['width'], $info['height'], $info['size'], $info['iso'], $info['aperture'], $info['make'], $info['model'], $info['shutter'], $info['focal'], $info['takestamp'], $path_thumb, $albumID, $public, $star, $checksum, $medium); + $query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum, medium) VALUES ('?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?')", $values); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) { + if ($returnOnError===true) return false; + Response::error('Could not save photo in database!'); + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return $id; + + } + + /** + * @return array|false Returns a subset of a photo when same photo exists or returns false on failure. + */ + private function exists($checksum, $photoID = null) { + + // Exclude $photoID from select when $photoID is set + if (isset($photoID)) $query = Database::prepare(Database::get(), "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' AND id <> '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum, $photoID)); + else $query = Database::prepare(Database::get(), "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum)); + + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + if ($result->num_rows===1) { + + $result = $result->fetch_object(); + + $return = array( + 'photo_name' => $result->url, + 'path' => LYCHEE_UPLOADS_BIG . $result->url, + 'path_thumb' => $result->thumbUrl, + 'medium' => $result->medium + ); + + return $return; + + } + + return false; + + } + + /** + * @return boolean Returns true when successful. + */ + private function createThumb($url, $filename, $type, $width, $height) { + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Quality of thumbnails + $thumbQuality = 90; + + // Size of the thumbnail + $newWidth = 200; + $newHeight = 200; + + $photoName = explode('.', $filename); + $newUrl = LYCHEE_UPLOADS_THUMB . $photoName[0] . '.jpeg'; + $newUrl2x = LYCHEE_UPLOADS_THUMB . $photoName[0] . '@2x.jpeg'; + + // Create thumbnails with Imagick + if(extension_loaded('imagick')&&Settings::get()['imagick']==='1') { + + // Read image + $thumb = new Imagick(); + $thumb->readImage($url); + $thumb->setImageCompressionQuality($thumbQuality); + $thumb->setImageFormat('jpeg'); + + // Copy image for 2nd thumb version + $thumb2x = clone $thumb; + + // Create 1st version + $thumb->cropThumbnailImage($newWidth, $newHeight); + $thumb->writeImage($newUrl); + $thumb->clear(); + $thumb->destroy(); + + // Create 2nd version + $thumb2x->cropThumbnailImage($newWidth*2, $newHeight*2); + $thumb2x->writeImage($newUrl2x); + $thumb2x->clear(); + $thumb2x->destroy(); + + } else { + + // Create image + $thumb = imagecreatetruecolor($newWidth, $newHeight); + $thumb2x = imagecreatetruecolor($newWidth*2, $newHeight*2); + + // Set position + if ($width<$height) { + $newSize = $width; + $startWidth = 0; + $startHeight = $height/2 - $width/2; + } else { + $newSize = $height; + $startWidth = $width/2 - $height/2; + $startHeight = 0; + } + + // Create new image + switch($type) { + case 'image/jpeg': $sourceImg = imagecreatefromjpeg($url); break; + case 'image/png': $sourceImg = imagecreatefrompng($url); break; + case 'image/gif': $sourceImg = imagecreatefromgif($url); break; + default: Log::error(Database::get(), __METHOD__, __LINE__, 'Type of photo is not supported'); + return false; + break; + } + + // Create thumb + fastImageCopyResampled($thumb, $sourceImg, 0, 0, $startWidth, $startHeight, $newWidth, $newHeight, $newSize, $newSize); + imagejpeg($thumb, $newUrl, $thumbQuality); + imagedestroy($thumb); + + // Create retina thumb + fastImageCopyResampled($thumb2x, $sourceImg, 0, 0, $startWidth, $startHeight, $newWidth*2, $newHeight*2, $newSize, $newSize); + imagejpeg($thumb2x, $newUrl2x, $thumbQuality); + imagedestroy($thumb2x); + + // Free memory + imagedestroy($sourceImg); + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return true; + + } + + /** + * Creates a smaller version of a photo when its size is bigger than a preset size. + * Photo must be big enough and Imagick must be installed and activated. + * @return boolean Returns true when successful. + */ + private function createMedium($url, $filename, $width, $height) { + + // Excepts the following: + // (string) $url = Path to the photo-file + // (string) $filename = Name of the photo-file + // (int) $width = Width of the photo + // (int) $height = Height of the photo + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Set to true when creation of medium-photo failed + $error = false; + + // Size of the medium-photo + // When changing these values, + // also change the size detection in the front-end + $newWidth = 1920; + $newHeight = 1080; + + // Check permissions + if (hasPermissions(LYCHEE_UPLOADS_MEDIUM)===false) { + + // Permissions are missing + Log::notice(Database::get(), __METHOD__, __LINE__, 'Skipped creation of medium-photo, because uploads/medium/ is missing or not readable and writable.'); + $error = true; + + } + + // Is photo big enough? + // Is Imagick installed and activated? + if (($error===false)&& + ($width>$newWidth||$height>$newHeight)&& + (extension_loaded('imagick')&&Settings::get()['imagick']==='1')) { + + $newUrl = LYCHEE_UPLOADS_MEDIUM . $filename; + + // Read image + $medium = new Imagick(); + $medium->readImage($url); + + // Adjust image + $medium->scaleImage($newWidth, $newHeight, true); + + // Save image + try { $medium->writeImage($newUrl); } + catch (ImagickException $err) { + Log::notice(Database::get(), __METHOD__, __LINE__, 'Could not save medium-photo (' . $err->getMessage() . ')'); + $error = true; + } + + $medium->clear(); + $medium->destroy(); + + } else { + + // Photo too small or + // Medium is deactivated or + // Imagick not installed + $error = true; + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($error===true) return false; + return true; + + } + + /** + * Rotates and flips a photo based on its EXIF orientation. + * @return array|false Returns an array with the new orientation, width, height or false on failure. + */ + public function adjustFile($path, array $info) { + + // Excepts the following: + // (string) $path = Path to the photo-file + // (array) $info = ['orientation', 'width', 'height'] + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + $swapSize = false; + + if (extension_loaded('imagick')&&Settings::get()['imagick']==='1') { + + switch ($info['orientation']) { + + case 3: + $rotateImage = 180; + break; + + case 6: + $rotateImage = 90; + $swapSize = true; + break; + + case 8: + $rotateImage = 270; + $swapSize = true; + break; + + default: + return false; + break; + + } + + if ($rotateImage!==0) { + $image = new Imagick(); + $image->readImage($path); + $image->rotateImage(new ImagickPixel(), $rotateImage); + $image->setImageOrientation(1); + $image->writeImage($path); + $image->clear(); + $image->destroy(); + } + + } else { + + $newWidth = $info['width']; + $newHeight = $info['height']; + $sourceImg = imagecreatefromjpeg($path); + + switch ($info['orientation']) { + + case 2: + // mirror + // not yet implemented + return false; + break; + + case 3: + $sourceImg = imagerotate($sourceImg, -180, 0); + break; + + case 4: + // rotate 180 and mirror + // not yet implemented + return false; + break; + + case 5: + // rotate 90 and mirror + // not yet implemented + return false; + break; + + case 6: + $sourceImg = imagerotate($sourceImg, -90, 0); + $newWidth = $info['height']; + $newHeight = $info['width']; + $swapSize = true; + break; + + case 7: + // rotate -90 and mirror + // not yet implemented + return false; + break; + + case 8: + $sourceImg = imagerotate($sourceImg, 90, 0); + $newWidth = $info['height']; + $newHeight = $info['width']; + $swapSize = true; + break; + + default: + return false; + break; + + } + + // Recreate photo + $newSourceImg = imagecreatetruecolor($newWidth, $newHeight); + imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight); + imagejpeg($newSourceImg, $path, 100); + + // Free memory + imagedestroy($sourceImg); + imagedestroy($newSourceImg); + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + // SwapSize should be true when the image has been rotated + // Return new dimensions in this case + if ($swapSize===true) { + $swapSize = $info['width']; + $info['width'] = $info['height']; + $info['height'] = $swapSize; + } + + return $info; + + } + + /** + * Rurns photo-attributes into a front-end friendly format. Note that some attributes remain unchanged. + * @return array Returns photo-attributes in a normalized structure. + */ + public static function prepareData(array $data) { + + // Excepts the following: + // (array) $data = ['id', 'title', 'tags', 'public', 'star', 'album', 'thumbUrl', 'takestamp', 'url'] + + // Init + $photo = null; + + // Set unchanged attributes + $photo['id'] = $data['id']; + $photo['title'] = $data['title']; + $photo['tags'] = $data['tags']; + $photo['public'] = $data['public']; + $photo['star'] = $data['star']; + $photo['album'] = $data['album']; + + // Parse urls + $photo['thumbUrl'] = LYCHEE_URL_UPLOADS_THUMB . $data['thumbUrl']; + $photo['url'] = LYCHEE_URL_UPLOADS_BIG . $data['url']; + + // Use takestamp as sysdate when possible + if (isset($data['takestamp'])&&$data['takestamp']!=='0') { + + // Use takestamp + $photo['cameraDate'] = '1'; + $photo['sysdate'] = strftime('%d %B %Y', $data['takestamp']); + + } else { + + // Use sysstamp from the id + $photo['cameraDate'] = '0'; + $photo['sysdate'] = strftime('%d %B %Y', substr($data['id'], 0, -4)); + + } + + return $photo; + + } + + /** + * @return array|false Returns an array with information about the photo or false on failure. + */ + public function get($albumID) { + + // Excepts the following: + // (string) $albumID = Album which is currently visible to the user + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Get photo + $query = Database::prepare(Database::get(), "SELECT * FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // Get photo object + $photo = $photos->fetch_assoc(); + + // Photo not found? + if ($photo===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified photo'); + return false; + } + + // Parse photo + $photo['sysdate'] = strftime('%d %b. %Y', substr($photo['id'], 0, -4)); + if (strlen($photo['takestamp'])>1) $photo['takedate'] = strftime('%d %b. %Y', $photo['takestamp']); + + // Parse medium + if ($photo['medium']==='1') $photo['medium'] = LYCHEE_URL_UPLOADS_MEDIUM . $photo['url']; + else $photo['medium'] = ''; + + // Parse paths + $photo['url'] = LYCHEE_URL_UPLOADS_BIG . $photo['url']; + $photo['thumbUrl'] = LYCHEE_URL_UPLOADS_THUMB . $photo['thumbUrl']; + + if ($albumID!='false') { + + // Only show photo as public when parent album is public + // Check if parent album is not 'Unsorted' + if ($photo['album']!=='0') { + + // Get album + $query = Database::prepare(Database::get(), "SELECT public FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $photo['album'])); + $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($albums===false) return false; + + // Get album object + $album = $albums->fetch_assoc(); + + // Photo not found? + if ($photo===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album'); + return false; + } + + // Parse album + $photo['public'] = ($album['public']==='1' ? '2' : $photo['public']); + + } + + $photo['original_album'] = $photo['album']; + $photo['album'] = $albumID; + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return $photo; + + } + + /** + * Reads and parses information and metadata out of a photo. + * @return array Returns an array of photo information and metadata. + */ + public function getInfo($url) { + + // Functions returns information and metadata of a photo + // Excepts the following: + // (string) $url = Path to photo-file + // Returns the following: + // (array) $return + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + $iptcArray = array(); + $info = getimagesize($url, $iptcArray); + + // General information + $return['type'] = $info['mime']; + $return['width'] = $info[0]; + $return['height'] = $info[1]; + + // Size + $size = filesize($url)/1024; + if ($size>=1024) $return['size'] = round($size/1024, 1) . ' MB'; + else $return['size'] = round($size, 1) . ' KB'; + + // IPTC Metadata Fallback + $return['title'] = ''; + $return['description'] = ''; + + // IPTC Metadata + if(isset($iptcArray['APP13'])) { + + $iptcInfo = iptcparse($iptcArray['APP13']); + if (is_array($iptcInfo)) { + + $temp = @$iptcInfo['2#105'][0]; + if (isset($temp)&&strlen($temp)>0) $return['title'] = $temp; + + $temp = @$iptcInfo['2#120'][0]; + if (isset($temp)&&strlen($temp)>0) $return['description'] = $temp; + + $temp = @$iptcInfo['2#005'][0]; + if (isset($temp)&&strlen($temp)>0&&$return['title']==='') $return['title'] = $temp; + + } + + } + + // EXIF Metadata Fallback + $return['orientation'] = ''; + $return['iso'] = ''; + $return['aperture'] = ''; + $return['make'] = ''; + $return['model'] = ''; + $return['shutter'] = ''; + $return['focal'] = ''; + $return['takestamp'] = 0; + + // Read EXIF + if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0); + else $exif = false; + + // EXIF Metadata + if ($exif!==false) { + + if (isset($exif['Orientation'])) $return['orientation'] = $exif['Orientation']; + else if (isset($exif['IFD0']['Orientation'])) $return['orientation'] = $exif['IFD0']['Orientation']; + + $temp = @$exif['ISOSpeedRatings']; + if (isset($temp)) $return['iso'] = $temp; + + $temp = @$exif['COMPUTED']['ApertureFNumber']; + if (isset($temp)) $return['aperture'] = $temp; + + $temp = @$exif['Make']; + if (isset($temp)) $return['make'] = trim($temp); + + $temp = @$exif['Model']; + if (isset($temp)) $return['model'] = trim($temp); + + $temp = @$exif['ExposureTime']; + if (isset($temp)) $return['shutter'] = $exif['ExposureTime'] . ' s'; + + $temp = @$exif['FocalLength']; + if (isset($temp)) { + if (strpos($temp, '/')!==FALSE) { + $temp = explode('/', $temp, 2); + $temp = $temp[0] / $temp[1]; + $temp = round($temp, 1); + $return['focal'] = $temp . ' mm'; + } + $return['focal'] = $temp . ' mm'; + } + + $temp = @$exif['DateTimeOriginal']; + if (isset($temp)) $return['takestamp'] = strtotime($temp); + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return $return; + + } + + /** + * Starts a download of a photo. + * @return resource|boolean Sends a ZIP-file or returns false on failure. + */ + public function getArchive() { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Get photo + $query = Database::prepare(Database::get(), "SELECT title, url FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // Get photo object + $photo = $photos->fetch_object(); + + // Photo not found? + if ($photo===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified photo'); + return false; + } + + // Get extension + $extension = getExtension($photo->url, false); + if (empty($extension)===true) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Invalid photo extension'); + return false; + } + + // Illicit chars + $badChars = array_merge( + array_map('chr', range(0,31)), + array("<", ">", ":", '"', "/", "\\", "|", "?", "*") + ); + + // Parse title + if ($photo->title=='') $photo->title = 'Untitled'; + + // Escape title + $photo->title = str_replace($badChars, '', $photo->title); + + // Set headers + header("Content-Type: application/octet-stream"); + header("Content-Disposition: attachment; filename=\"" . $photo->title . $extension . "\""); + header("Content-Length: " . filesize(LYCHEE_UPLOADS_BIG . $photo->url)); + + // Send file + readfile(LYCHEE_UPLOADS_BIG . $photo->url); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return true; + + } + + /** + * Sets the title of a photo. + * @return boolean Returns true when successful. + */ + public function setTitle($title = 'Untitled') { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Set title + $query = Database::prepare(Database::get(), "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $title, $this->photoIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * Sets the description of a photo. + * @return boolean Returns true when successful. + */ + public function setDescription($description) { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Set description + $query = Database::prepare(Database::get(), "UPDATE ? SET description = '?' WHERE id IN ('?')", array(LYCHEE_TABLE_PHOTOS, $description, $this->photoIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * Toggles the star property of a photo. + * @return boolean Returns true when successful. + */ + public function setStar() { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Init vars + $error = false; + + // Get photos + $query = Database::prepare(Database::get(), "SELECT id, star FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // For each photo + while ($photo = $photos->fetch_object()) { + + // Invert star + $star = ($photo->star==0 ? 1 : 0); + + // Set star + $query = Database::prepare(Database::get(), "UPDATE ? SET star = '?' WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $star, $photo->id)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) $error = true; + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($error===true) return false; + return true; + + } + + /** + * Checks if photo or parent album is public. + * @return integer 0 = Photo private and parent album private + * 1 = Album public, but password incorrect + * 2 = Photo public or album public and password correct + */ + public function getPublic($password) { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Get photo + $query = Database::prepare(Database::get(), "SELECT public, album FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return 0; + + // Get photo object + $photo = $photos->fetch_object(); + + // Photo not found? + if ($photo===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified photo'); + return false; + } + + // Check if public + if ($photo->public==='1') { + + // Photo public + return 2; + + } else { + + // Check if album public + $album = new Album($photo->album); + $agP = $album->getPublic(); + $acP = $album->checkPassword($password); + + // Album public and password correct + if ($agP===true&&$acP===true) return 2; + + // Album public, but password incorrect + if ($agP===true&&$acP===false) return 1; + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + // Photo private + return 0; + + } + + /** + * Toggles the public property of a photo. + * @return boolean Returns true when successful. + */ + public function setPublic() { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Get public + $query = Database::prepare(Database::get(), "SELECT public FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // Get photo object + $photo = $photos->fetch_object(); + + // Photo not found? + if ($photo===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified photo'); + return false; + } + + // Invert public + $public = ($photo->public==0 ? 1 : 0); + + // Set public + $query = Database::prepare(Database::get(), "UPDATE ? SET public = '?' WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $public, $this->photoIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * Sets the parent album of a photo. + * @return boolean Returns true when successful. + */ + function setAlbum($albumID) { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Set album + $query = Database::prepare(Database::get(), "UPDATE ? SET album = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->photoIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * Sets the tags of a photo. + * @return boolean Returns true when successful. + */ + public function setTags($tags) { + + // Excepts the following: + // (string) $tags = Comma separated list of tags with a maximum length of 1000 chars + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Parse tags + $tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags); + $tags = preg_replace('/,$|^,|(\ ){0,}$/', '', $tags); + + // Set tags + $query = Database::prepare(Database::get(), "UPDATE ? SET tags = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $tags, $this->photoIDs)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($result===false) return false; + return true; + + } + + /** + * Duplicates a photo. + * @return boolean Returns true when successful. + */ + public function duplicate() { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Init vars + $error = false; + + // Get photos + $query = Database::prepare(Database::get(), "SELECT id, checksum FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // For each photo + while ($photo = $photos->fetch_object()) { + + // Generate id + $id = generateID(); + + // Duplicate entry + $values = array(LYCHEE_TABLE_PHOTOS, $id, LYCHEE_TABLE_PHOTOS, $photo->id); + $query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum) SELECT '?' AS id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum FROM ? WHERE id = '?'", $values); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) $error = true; + + } + + if ($error===true) return false; + return true; + + } + + /** + * Deletes a photo with all its data and files. + * @return boolean Returns true when successful. + */ + public function delete() { + + // Check dependencies + Validator::required(isset($this->photoIDs), __METHOD__); + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + // Init vars + $error = false; + + // Get photos + $query = Database::prepare(Database::get(), "SELECT id, url, thumbUrl, checksum FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); + $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($photos===false) return false; + + // For each photo + while ($photo = $photos->fetch_object()) { + + // Check if other photos are referring to this images + // If so, only delete the db entry + if ($this->exists($photo->checksum, $photo->id)===false) { + + // Get retina thumb url + $thumbUrl2x = explode(".", $photo->thumbUrl); + $thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1]; + + // Delete big + if (file_exists(LYCHEE_UPLOADS_BIG . $photo->url)&&!unlink(LYCHEE_UPLOADS_BIG . $photo->url)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not delete photo in uploads/big/'); + $error = true; + } + + // Delete medium + if (file_exists(LYCHEE_UPLOADS_MEDIUM . $photo->url)&&!unlink(LYCHEE_UPLOADS_MEDIUM . $photo->url)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not delete photo in uploads/medium/'); + $error = true; + } + + // Delete thumb + if (file_exists(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)&&!unlink(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not delete photo in uploads/thumb/'); + $error = true; + } + + // Delete thumb@2x + if (file_exists(LYCHEE_UPLOADS_THUMB . $thumbUrl2x)&&!unlink(LYCHEE_UPLOADS_THUMB . $thumbUrl2x)) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not delete high-res photo in uploads/thumb/'); + $error = true; + } + + } + + // Delete db entry + $query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $photo->id)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) $error = true; + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + if ($error===true) return false; + return true; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Plugins.php b/php/Modules/Plugins.php new file mode 100644 index 0000000..ed17f02 --- /dev/null +++ b/php/Modules/Plugins.php @@ -0,0 +1,99 @@ +attach(new $plugin); + + } + + return true; + + } + + public function attach(SplObserver $observer) { + + if (!isset($observer)) return false; + + // Add observer + $this->observers[] = $observer; + + return true; + + } + + public function detach(SplObserver $observer) { + + if (!isset($observer)) return false; + + // Remove observer + $key = array_search($observer, $this->observers, true); + if ($key) unset($this->observers[$key]); + + return true; + + } + + public function notify() { + + // Notify each observer + foreach ($this->observers as $value) $value->update($this); + + return true; + + } + + public function activate($name, $location, array $args) { + + if (!isset($name, $location, $args)) return false; + + // Parse + $location = ($location===0 ? 'before' : 'after'); + $action = $name . ":" . $location; + + // Save vars + $this->action = $action; + $this->args = $args; + + // Notify observers + $this->notify(); + + return true; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Response.php b/php/Modules/Response.php new file mode 100644 index 0000000..6ae5f30 --- /dev/null +++ b/php/Modules/Response.php @@ -0,0 +1,25 @@ +activate(__METHOD__, 0, func_get_args()); + + // Return settings + $return['config'] = Settings::get(); + + // Path to Lychee for the server-import dialog + $return['config']['location'] = LYCHEE; + + // Remove sensitive from response + unset($return['config']['username']); + unset($return['config']['password']); + unset($return['config']['identifier']); + + // Check if login credentials exist and login if they don't + if ($this->noLogin()===true) { + $public = false; + $return['config']['login'] = false; + } else { + $return['config']['login'] = true; + } + + if ($public===false) { + + // Logged in + $return['status'] = LYCHEE_STATUS_LOGGEDIN; + + } else { + + // Logged out + $return['status'] = LYCHEE_STATUS_LOGGEDOUT; + + // Unset unused vars + unset($return['config']['skipDuplicates']); + unset($return['config']['sortingAlbums']); + unset($return['config']['sortingPhotos']); + unset($return['config']['dropboxKey']); + unset($return['config']['login']); + unset($return['config']['location']); + unset($return['config']['imagick']); + unset($return['config']['plugins']); + + } + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return $return; + + } + + /** + * Sets the session values when username and password correct. + * @return boolean Returns true when login was successful. + */ + public function login($username, $password) { + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + $username_crypt = crypt($username, Settings::get()['username']); + $password_crypt = crypt($password, Settings::get()['password']); + + // Check login with crypted hash + if (Settings::get()['username']===$username_crypt&& + Settings::get()['password']===$password_crypt) { + $_SESSION['login'] = true; + $_SESSION['identifier'] = Settings::get()['identifier']; + Log::notice(Database::get(), __METHOD__, __LINE__, 'User (' . $username . ') has logged in from ' . $_SERVER['REMOTE_ADDR']); + return true; + } + + // No login + if ($this->noLogin()===true) return true; + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + // Log failed log in + Log::error(Database::get(), __METHOD__, __LINE__, 'User (' . $username . ') has tried to log in from ' . $_SERVER['REMOTE_ADDR']); + + return false; + + } + + /** + * Sets the session values when no there is no username and password in the database. + * @return boolean Returns true when no login was found. + */ + private function noLogin() { + + // Check if login credentials exist and login if they don't + if (Settings::get()['username']===''&& + Settings::get()['password']==='') { + $_SESSION['login'] = true; + $_SESSION['identifier'] = Settings::get()['identifier']; + return true; + } + + return false; + + } + + /** + * Unsets the session values. + * @return boolean Returns true when logout was successful. + */ + public function logout() { + + // Call plugins + Plugins::get()->activate(__METHOD__, 0, func_get_args()); + + session_unset(); + session_destroy(); + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + return true; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Settings.php b/php/Modules/Settings.php new file mode 100755 index 0000000..018dc7c --- /dev/null +++ b/php/Modules/Settings.php @@ -0,0 +1,226 @@ +fetch_object()) $return[$setting->key] = $setting->value; + + // Convert plugins to array + $return['plugins'] = explode(';', $return['plugins']); + + self::$cache = $return; + + return $return; + + } + + /** + * @return boolean Returns true when successful. + */ + private static function set($key, $value, $row = false) { + + if ($row===false) { + + $query = Database::prepare(Database::get(), "UPDATE ? SET value = '?' WHERE `key` = '?'", array(LYCHEE_TABLE_SETTINGS, $value, $key)); + + } elseif ($row===true) { + + // Do not prepare $value because it has already been escaped or is a true statement + $query = Database::prepare(Database::get(), "UPDATE ? SET value = '$value' WHERE `key` = '?'", array(LYCHEE_TABLE_SETTINGS, $key)); + + } else { + + return false; + + } + + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + return true; + + } + + /** + * Sets the username and password when current password is correct. + * Exits on error. + * @return true Returns true when successful. + */ + public static function setLogin($oldPassword = '', $username, $password) { + + if ($oldPassword===self::get()['password']||self::get()['password']===crypt($oldPassword, self::get()['password'])) { + + // Save username + if (self::setUsername($username)===false) Response::error('Updating username failed!'); + + // Save password + if (self::setPassword($password)===false) Response::error('Updating password failed!'); + + return true; + + } + + Response::error('Current password entered incorrectly!'); + + } + + /** + * Sets a new username. + * @return boolean Returns true when successful. + */ + private static function setUsername($username) { + + // Check dependencies + Validator::required(isset($username), __METHOD__); + + // Hash username + $username = getHashedString($username); + + // Execute query + // Do not prepare $username because it is hashed and save + // Preparing (escaping) the username would destroy the hash + if (self::set('username', $username, true)===false) return false; + return true; + + } + + /** + * Sets a new username. + * @return boolean Returns true when successful. + */ + private static function setPassword($password) { + + // Check dependencies + Validator::required(isset($password), __METHOD__); + + // Hash password + $password = getHashedString($password); + + // Do not prepare $password because it is hashed and save + // Preparing (escaping) the password would destroy the hash + if (self::set('password', $password, true)===false) return false; + return true; + + } + + /** + * Sets a new dropboxKey. + * @return boolean Returns true when successful. + */ + public static function setDropboxKey($dropboxKey) { + + if (strlen($dropboxKey)<1||strlen($dropboxKey)>50) { + Log::notice(Database::get(), __METHOD__, __LINE__, 'Dropbox key is either too short or too long'); + return false; + } + + if (self::set('dropboxKey', $dropboxKey)===false) return false; + return true; + + } + + /** + * Sets a new sorting for the photos. + * @return boolean Returns true when successful. + */ + public static function setSortingPhotos($type, $order) { + + $sorting = 'ORDER BY '; + + // Set row + switch ($type) { + + case 'id': $sorting .= 'id'; break; + case 'title': $sorting .= 'title'; break; + case 'description': $sorting .= 'description'; break; + case 'public': $sorting .= 'public'; break; + case 'type': $sorting .= 'type'; break; + case 'star': $sorting .= 'star'; break; + case 'takestamp': $sorting .= 'takestamp'; break; + default: Log::error(Database::get(), __METHOD__, __LINE__, 'Could not update settings. Unknown type for sorting.'); + return false; + break; + + } + + $sorting .= ' '; + + // Set order + switch ($order) { + + case 'ASC': $sorting .= 'ASC'; break; + case 'DESC': $sorting .= 'DESC'; break; + default: Log::error(Database::get(), __METHOD__, __LINE__, 'Could not update settings. Unknown order for sorting.'); + return false; + break; + + } + + // Do not prepare $sorting because it is a true statement + // Preparing (escaping) the sorting would destroy it + // $sorting is save and can't contain user-input + if (self::set('sortingPhotos', $sorting, true)===false) return false; + return true; + + } + + /** + * Sets a new sorting for the albums. + * @return boolean Returns true when successful. + */ + public static function setSortingAlbums($type, $order) { + + $sorting = 'ORDER BY '; + + // Set row + switch ($type) { + + case 'id': $sorting .= 'id'; break; + case 'title': $sorting .= 'title'; break; + case 'description': $sorting .= 'description'; break; + case 'public': $sorting .= 'public'; break; + default: Log::error(Database::get(), __METHOD__, __LINE__, 'Could not update settings. Unknown type for sorting.'); + return false; + break; + + } + + $sorting .= ' '; + + // Set order + switch ($order) { + + case 'ASC': $sorting .= 'ASC'; break; + case 'DESC': $sorting .= 'DESC'; break; + default: Log::error(Database::get(), __METHOD__, __LINE__, 'Could not update settings. Unknown order for sorting.'); + return false; + break; + + } + + // Do not prepare $sorting because it is a true statement + // Preparing (escaping) the sorting would destroy it + // $sorting is save and can't contain user-input + if (self::set('sortingAlbums', $sorting, true)===false) return false; + return true; + + } + +} + +?> \ No newline at end of file diff --git a/php/Modules/Validator.php b/php/Modules/Validator.php new file mode 100644 index 0000000..2090fa2 --- /dev/null +++ b/php/Modules/Validator.php @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/php/access/Access.php b/php/access/Access.php deleted file mode 100644 index 8a145c8..0000000 --- a/php/access/Access.php +++ /dev/null @@ -1,29 +0,0 @@ -database = $database; - $this->plugins = $plugins; - $this->settings = $settings; - - return true; - - } - -} - -?> \ No newline at end of file diff --git a/php/access/Admin.php b/php/access/Admin.php deleted file mode 100644 index e70aa07..0000000 --- a/php/access/Admin.php +++ /dev/null @@ -1,321 +0,0 @@ -getAlbums(); break; - case 'Album::get': $this->getAlbum(); break; - case 'Album::add': $this->addAlbum(); break; - case 'Album::setTitle': $this->setAlbumTitle(); break; - case 'Album::setDescription': $this->setAlbumDescription(); break; - case 'Album::setPublic': $this->setAlbumPublic(); break; - case 'Album::delete': $this->deleteAlbum(); break; - case 'Album::merge': $this->mergeAlbums(); break; - - # Photo functions - case 'Photo::get': $this->getPhoto(); break; - case 'Photo::setTitle': $this->setPhotoTitle(); break; - case 'Photo::setDescription': $this->setPhotoDescription(); break; - case 'Photo::setStar': $this->setPhotoStar(); break; - case 'Photo::setPublic': $this->setPhotoPublic(); break; - case 'Photo::setAlbum': $this->setPhotoAlbum(); break; - case 'Photo::setTags': $this->setPhotoTags(); break; - case 'Photo::duplicate': $this->duplicatePhoto(); break; - case 'Photo::delete': $this->deletePhoto(); break; - - # Add functions - case 'Photo::add': $this->upload(); break; - case 'Import::url': $this->importUrl(); break; - case 'Import::server': $this->importServer(); break; - - # Search functions - case 'search': $this->search(); break; - - # Session functions - case 'Session::init': $this->init(); break; - case 'Session::login': $this->login(); break; - case 'Session::logout': $this->logout(); break; - - # Settings functions - case 'Settings::setLogin': $this->setLogin(); break; - case 'Settings::setSorting': $this->setSorting(); break; - case 'Settings::setDropboxKey': $this->setDropboxKey(); break; - - # $_GET functions - case 'Album::getArchive': $this->getAlbumArchive(); break; - case 'Photo::getArchive': $this->getPhotoArchive(); break; - - # Error - default: exit('Error: Function not found! Please check the spelling of the called function.'); - return false; break; - - } - - return true; - - } - - # Album functions - - private function getAlbums() { - - $album = new Album($this->database, $this->plugins, $this->settings, null); - echo json_encode($album->getAll(false)); - - } - - private function getAlbum() { - - Module::dependencies(isset($_POST['albumID'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']); - echo json_encode($album->get()); - - } - - private function addAlbum() { - - Module::dependencies(isset($_POST['title'])); - $album = new Album($this->database, $this->plugins, $this->settings, null); - echo $album->add($_POST['title']); - - } - - private function setAlbumTitle() { - - Module::dependencies(isset($_POST['albumIDs'], $_POST['title'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumIDs']); - echo $album->setTitle($_POST['title']); - - } - - private function setAlbumDescription() { - - Module::dependencies(isset($_POST['albumID'], $_POST['description'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']); - echo $album->setDescription($_POST['description']); - - } - - private function setAlbumPublic() { - - Module::dependencies(isset($_POST['albumID'], $_POST['password'], $_POST['visible'], $_POST['downloadable'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']); - echo $album->setPublic($_POST['public'], $_POST['password'], $_POST['visible'], $_POST['downloadable']); - - } - - private function deleteAlbum() { - - Module::dependencies(isset($_POST['albumIDs'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumIDs']); - echo $album->delete(); - - } - - private function mergeAlbums() { - - Module::dependencies(isset($_POST['albumIDs'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumIDs']); - echo $album->merge(); - - } - - # Photo functions - - private function getPhoto() { - - Module::dependencies(isset($_POST['photoID'], $_POST['albumID'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']); - echo json_encode($photo->get($_POST['albumID'])); - - } - - private function setPhotoTitle() { - - Module::dependencies(isset($_POST['photoIDs'], $_POST['title'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']); - echo $photo->setTitle($_POST['title']); - - } - - private function setPhotoDescription() { - - Module::dependencies(isset($_POST['photoID'], $_POST['description'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']); - echo $photo->setDescription($_POST['description']); - - } - - private function setPhotoStar() { - - Module::dependencies(isset($_POST['photoIDs'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']); - echo $photo->setStar(); - - } - - private function setPhotoPublic() { - - Module::dependencies(isset($_POST['photoID'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']); - echo $photo->setPublic(); - - } - - private function setPhotoAlbum() { - - Module::dependencies(isset($_POST['photoIDs'], $_POST['albumID'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']); - echo $photo->setAlbum($_POST['albumID']); - - } - - private function setPhotoTags() { - - Module::dependencies(isset($_POST['photoIDs'], $_POST['tags'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']); - echo $photo->setTags($_POST['tags']); - - } - - private function duplicatePhoto() { - - Module::dependencies(isset($_POST['photoIDs'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']); - echo $photo->duplicate(); - - } - - private function deletePhoto() { - - Module::dependencies(isset($_POST['photoIDs'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']); - echo $photo->delete(); - - } - - # Add functions - - private function upload() { - - Module::dependencies(isset($_FILES, $_POST['albumID'], $_POST['tags'])); - $photo = new Photo($this->database, $this->plugins, $this->settings, null); - echo $photo->add($_FILES, $_POST['albumID'], '', $_POST['tags']); - - } - - private function importUrl() { - - Module::dependencies(isset($_POST['url'], $_POST['albumID'])); - $import = new Import($this->database, $this->plugins, $this->settings); - echo $import->url($_POST['url'], $_POST['albumID']); - - } - - private function importServer() { - - Module::dependencies(isset($_POST['albumID'], $_POST['path'])); - $import = new Import($this->database, $this->plugins, $this->settings); - echo $import->server($_POST['path'], $_POST['albumID']); - - } - - # Search function - - private function search() { - - Module::dependencies(isset($_POST['term'])); - echo json_encode(search($this->database, $this->settings, $_POST['term'])); - - } - - # Session functions - - private function init() { - - global $dbName; - - Module::dependencies(isset($_POST['version'])); - $session = new Session($this->plugins, $this->settings); - echo json_encode($session->init($this->database, $dbName, false, $_POST['version'])); - - } - - private function login() { - - Module::dependencies(isset($_POST['user'], $_POST['password'])); - $session = new Session($this->plugins, $this->settings); - echo $session->login($_POST['user'], $_POST['password']); - - } - - private function logout() { - - $session = new Session($this->plugins, $this->settings); - echo $session->logout(); - - } - - # Settings functions - - private function setLogin() { - - Module::dependencies(isset($_POST['username'], $_POST['password'])); - if (!isset($_POST['oldPassword'])) $_POST['oldPassword'] = ''; - $this->settings = new Settings($this->database); - echo $this->settings->setLogin($_POST['oldPassword'], $_POST['username'], $_POST['password']); - - } - - private function setSorting() { - - Module::dependencies(isset($_POST['typeAlbums'], $_POST['orderAlbums'], $_POST['typePhotos'], $_POST['orderPhotos'])); - $this->settings = new Settings($this->database); - - $sA = $this->settings->setSortingAlbums($_POST['typeAlbums'], $_POST['orderAlbums']); - $sP = $this->settings->setSortingPhotos($_POST['typePhotos'], $_POST['orderPhotos']); - - if ($sA===true&&$sP===true) echo true; - else echo false; - - } - - private function setDropboxKey() { - - Module::dependencies(isset($_POST['key'])); - $this->settings = new Settings($this->database); - echo $this->settings->setDropboxKey($_POST['key']); - - } - - # Get functions - - private function getAlbumArchive() { - - Module::dependencies(isset($_GET['albumID'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_GET['albumID']); - $album->getArchive(); - - } - - private function getPhotoArchive() { - - Module::dependencies(isset($_GET['photoID'])); - $photo = new Photo($this->database, $this->plugins, null, $_GET['photoID']); - $photo->getArchive(); - - } - -} \ No newline at end of file diff --git a/php/access/Guest.php b/php/access/Guest.php deleted file mode 100644 index 611d310..0000000 --- a/php/access/Guest.php +++ /dev/null @@ -1,180 +0,0 @@ -getAlbums(); break; - case 'Album::get': $this->getAlbum(); break; - case 'Album::getPublic': $this->checkAlbumAccess(); break; - - # Photo functions - case 'Photo::get': $this->getPhoto(); break; - - # Session functions - case 'Session::init': $this->init(); break; - case 'Session::login': $this->login(); break; - case 'Session::logout': $this->logout(); break; - - # $_GET functions - case 'Album::getArchive': $this->getAlbumArchive(); break; - case 'Photo::getArchive': $this->getPhotoArchive(); break; - - # Error - default: exit('Error: Function not found! Please check the spelling of the called function.'); - break; - - } - - return true; - - } - - # Album functions - - private function getAlbums() { - - $album = new Album($this->database, $this->plugins, $this->settings, null); - echo json_encode($album->getAll(true)); - - } - - private function getAlbum() { - - Module::dependencies(isset($_POST['albumID'], $_POST['password'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']); - - if ($album->getPublic()) { - - # Album public - if ($album->checkPassword($_POST['password'])) echo json_encode($album->get()); - else echo 'Warning: Wrong password!'; - - } else { - - # Album private - echo 'Warning: Album private!'; - - } - - } - - private function checkAlbumAccess() { - - Module::dependencies(isset($_POST['albumID'], $_POST['password'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']); - - if ($album->getPublic()) { - - # Album public - if ($album->checkPassword($_POST['password'])) echo true; - else echo false; - - } else { - - # Album private - echo false; - - } - - } - - # Photo functions - - private function getPhoto() { - - Module::dependencies(isset($_POST['photoID'], $_POST['albumID'], $_POST['password'])); - $photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']); - - $pgP = $photo->getPublic($_POST['password']); - - if ($pgP===2) echo json_encode($photo->get($_POST['albumID'])); - else if ($pgP===1) echo 'Warning: Wrong password!'; - else if ($pgP===0) echo 'Warning: Photo private!'; - - } - - # Session functions - - private function init() { - - global $dbName; - - $session = new Session($this->plugins, $this->settings); - echo json_encode($session->init($this->database, $dbName, true, $_POST['version'])); - - } - - private function login() { - - Module::dependencies(isset($_POST['user'], $_POST['password'])); - $session = new Session($this->plugins, $this->settings); - echo $session->login($_POST['user'], $_POST['password']); - - } - - private function logout() { - - $session = new Session($this->plugins, $this->settings); - echo $session->logout(); - - } - - # $_GET functions - - private function getAlbumArchive() { - - Module::dependencies(isset($_GET['albumID'], $_GET['password'])); - $album = new Album($this->database, $this->plugins, $this->settings, $_GET['albumID']); - - if ($album->getPublic()&&$album->getDownloadable()) { - - # Album Public - if ($album->checkPassword($_GET['password'])) $album->getArchive(); - else exit('Warning: Wrong password!'); - - } else { - - # Album Private - exit('Warning: Album private or not downloadable!'); - - } - - } - - private function getPhotoArchive() { - - Module::dependencies(isset($_GET['photoID'], $_GET['password'])); - $photo = new Photo($this->database, $this->plugins, null, $_GET['photoID']); - - $pgP = $photo->getPublic($_GET['password']); - - # Photo Download - if ($pgP===2) { - - # Photo Public - $photo->getArchive(); - - } else { - - # Photo Private - exit('Warning: Photo private or password incorrect!'); - - } - - } - -} - -?> diff --git a/php/access/Installation.php b/php/access/Installation.php deleted file mode 100644 index ef1b21f..0000000 --- a/php/access/Installation.php +++ /dev/null @@ -1,47 +0,0 @@ -dbCreateConfig(); break; - - # Error - default: $this->init(); break; - - } - - return true; - - } - - private function dbCreateConfig() { - - Module::dependencies(isset($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix'])); - echo Database::createConfig($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix']); - - } - - private function init() { - - $return = array( - 'status' => LYCHEE_STATUS_NOCONFIG - ); - - echo json_encode($return); - - } - -} - -?> \ No newline at end of file diff --git a/php/api.php b/php/api.php deleted file mode 100755 index d043a4d..0000000 --- a/php/api.php +++ /dev/null @@ -1,93 +0,0 @@ -check($_POST['function']); - - exit(); - - } - - # Define the table prefix - if (!isset($dbTablePrefix)) $dbTablePrefix = ''; - defineTablePrefix($dbTablePrefix); - - # Connect to database - $database = Database::connect($dbHost, $dbUser, $dbPassword, $dbName); - - # Load settings - $settings = new Settings($database); - $settings = $settings->get(); - - # Init plugins - $plugins = explode(';', $settings['plugins']); - $plugins = new Plugins($plugins, $database, $settings); - - # Validate parameters - if (isset($_POST['albumIDs'])&&preg_match('/^[0-9\,]{1,}$/', $_POST['albumIDs'])!==1) exit('Error: Wrong parameter type for albumIDs!'); - if (isset($_POST['photoIDs'])&&preg_match('/^[0-9\,]{1,}$/', $_POST['photoIDs'])!==1) exit('Error: Wrong parameter type for photoIDs!'); - if (isset($_POST['albumID'])&&preg_match('/^[0-9sfr]{1,}$/', $_POST['albumID'])!==1) exit('Error: Wrong parameter type for albumID!'); - if (isset($_POST['photoID'])&&preg_match('/^[0-9]{14}$/', $_POST['photoID'])!==1) exit('Error: Wrong parameter type for photoID!'); - - # Function for switch statement - if (isset($_POST['function'])) $fn = $_POST['function']; - else $fn = $_GET['function']; - - if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&& - (isset($_SESSION['identifier'])&&$_SESSION['identifier']===$settings['identifier'])) { - - ### - # Admin Access - # Full access to Lychee. Only with correct password/session. - ### - - define('LYCHEE_ACCESS_ADMIN', true); - - $admin = new Admin($database, $plugins, $settings); - $admin->check($fn); - - } else { - - ### - # Guest Access - # Access to view all public folders and photos in Lychee. - ### - - define('LYCHEE_ACCESS_GUEST', true); - - $guest = new Guest($database, $plugins, $settings); - $guest->check($fn); - - } - -} else { - - exit('Error: Called function not found!'); - -} - -?> diff --git a/php/autoload.php b/php/autoload.php index 91df00c..64356af 100644 --- a/php/autoload.php +++ b/php/autoload.php @@ -1,33 +1,24 @@ \ No newline at end of file diff --git a/php/database/albums_table.sql b/php/database/albums_table.sql index b949e2e..ea94d41 100644 --- a/php/database/albums_table.sql +++ b/php/database/albums_table.sql @@ -1,9 +1,8 @@ # Dump of table lychee_albums -# Version 2.5 # ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `?` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` bigint(14) NOT NULL, `title` varchar(100) NOT NULL DEFAULT '', `description` varchar(1000) DEFAULT '', `sysstamp` int(11) NOT NULL, diff --git a/php/database/log_table.sql b/php/database/log_table.sql index 800b00c..c5869bf 100644 --- a/php/database/log_table.sql +++ b/php/database/log_table.sql @@ -1,5 +1,4 @@ # Dump of table lychee_log -# Version 2.5 # ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `?` ( diff --git a/php/database/photos_table.sql b/php/database/photos_table.sql index b8d5ef9..bd265e8 100644 --- a/php/database/photos_table.sql +++ b/php/database/photos_table.sql @@ -1,5 +1,4 @@ # Dump of table lychee_photos -# Version 2.5 # ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `?` ( diff --git a/php/database/settings_content.sql b/php/database/settings_content.sql index cbcc9d6..18067ff 100644 --- a/php/database/settings_content.sql +++ b/php/database/settings_content.sql @@ -1,5 +1,4 @@ # Content of table lychee_settings -# Version 2.5 # ------------------------------------------------------------ INSERT INTO `?` (`key`, `value`) @@ -7,11 +6,9 @@ VALUES ('version',''), ('username',''), ('password',''), - ('thumbQuality','90'), ('checkForUpdates','1'), ('sortingPhotos','ORDER BY id DESC'), ('sortingAlbums','ORDER BY id DESC'), - ('medium','1'), ('imagick','1'), ('dropboxKey',''), ('identifier',''), diff --git a/php/database/settings_table.sql b/php/database/settings_table.sql index 80eccce..bdc1cf2 100644 --- a/php/database/settings_table.sql +++ b/php/database/settings_table.sql @@ -1,5 +1,4 @@ # Dump of table lychee_settings -# Version 2.5 # ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `?` ( diff --git a/php/database/update_020100.php b/php/database/update_020100.php deleted file mode 100644 index a9c9c9b..0000000 --- a/php/database/update_020100.php +++ /dev/null @@ -1,44 +0,0 @@ -query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `tags` VARCHAR( 1000 ) NULL DEFAULT ''", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'dropboxKey' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('dropboxKey', '')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'version' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('version', '020100')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} else { - if (Database::setVersion($database, '020100')===false) return false; -} - -?> \ No newline at end of file diff --git a/php/database/update_020101.php b/php/database/update_020101.php deleted file mode 100644 index 435477e..0000000 --- a/php/database/update_020101.php +++ /dev/null @@ -1,20 +0,0 @@ -query($query); -if (!$result) { - Log::error($database, 'update_020101', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set version -if (Database::setVersion($database, '020101')===false) return false; - -?> \ No newline at end of file diff --git a/php/database/update_020200.php b/php/database/update_020200.php deleted file mode 100644 index 161ccc2..0000000 --- a/php/database/update_020200.php +++ /dev/null @@ -1,23 +0,0 @@ -query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `visible` TINYINT(1) NOT NULL DEFAULT 1", array(LYCHEE_TABLE_ALBUMS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020200', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Set version -if (Database::setVersion($database, '020200')===false) return false; - -?> \ No newline at end of file diff --git a/php/database/update_020500.php b/php/database/update_020500.php deleted file mode 100644 index 9892961..0000000 --- a/php/database/update_020500.php +++ /dev/null @@ -1,156 +0,0 @@ -query($query); -if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('plugins', '')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Add `takestamp` -$query = Database::prepare($database, "SELECT `takestamp` FROM `?` LIMIT 1;", array(LYCHEE_TABLE_PHOTOS)); -if (!$database->query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `takestamp` INT(11) DEFAULT NULL", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Convert to `takestamp` -$query = Database::prepare($database, "SELECT `takedate`, `taketime` FROM `?` LIMIT 1;", array(LYCHEE_TABLE_PHOTOS)); -if ($database->query($query)) { - $query = Database::prepare($database, "SELECT `id`, `takedate`, `taketime` FROM `?` WHERE `takedate` <> '' AND `taketime` <> ''", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } - while ($photo = $result->fetch_object()) { - $takestamp = strtotime($photo->takedate . $photo->taketime); - $query = Database::prepare($database, "UPDATE `?` SET `takestamp` = '?' WHERE `id` = '?'", array(LYCHEE_TABLE_PHOTOS, $takestamp, $photo->id)); - $database->query($query); - } - $query = Database::prepare($database, "ALTER TABLE `?` DROP COLUMN `takedate`;", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - $query = Database::prepare($database, "ALTER TABLE `?` DROP COLUMN `taketime`", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); -} - -# Remove `import_name` -$query = Database::prepare($database, "SELECT `import_name` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS)); -if ($database->query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` DROP COLUMN `import_name`", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); -} - -# Remove `sysdate` and `systime` -$query = Database::prepare($database, "SELECT `sysdate`, `systime` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS)); -if ($database->query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` DROP COLUMN `sysdate`", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - $query = Database::prepare($database, "ALTER TABLE `?` DROP COLUMN `systime`", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); -} - -# Add `sysstamp` -$query = Database::prepare($database, "SELECT `sysstamp` FROM `?` LIMIT 1", array(LYCHEE_TABLE_ALBUMS)); -if (!$database->query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `sysstamp` INT(11) DEFAULT NULL", array(LYCHEE_TABLE_ALBUMS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Convert to `sysstamp` -$query = Database::prepare($database, "SELECT `sysdate` FROM `?` LIMIT 1", array(LYCHEE_TABLE_ALBUMS)); -if ($database->query($query)) { - $query = Database::prepare($database, "SELECT `id`, `sysdate` FROM `?`", array(LYCHEE_TABLE_ALBUMS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } - while ($album = $result->fetch_object()) { - $sysstamp = strtotime($album->sysdate); - $query = Database::prepare($database, "UPDATE `?` SET `sysstamp` = '?' WHERE `id` = '?'", array(LYCHEE_TABLE_ALBUMS, $sysstamp, $album->id)); - $database->query($query); - } - $query = Database::prepare($database, "ALTER TABLE `?` DROP COLUMN `sysdate`", array(LYCHEE_TABLE_ALBUMS)); - $result = $database->query($query); -} - -# Set character of database -$result = $database->query("ALTER DATABASE $dbName CHARACTER SET utf8 COLLATE utf8_general_ci;"); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set character -$query = Database::prepare($database, "ALTER TABLE `?` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci", array(LYCHEE_TABLE_ALBUMS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set character -$query = Database::prepare($database, "ALTER TABLE `?` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci", array(LYCHEE_TABLE_PHOTOS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set character -$query = Database::prepare($database, "ALTER TABLE `?` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set album password length to 100 (for longer hashes) -$query = Database::prepare($database, "ALTER TABLE `?` CHANGE `password` `password` VARCHAR(100)", array(LYCHEE_TABLE_ALBUMS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set make length to 50 -$query = Database::prepare($database, "ALTER TABLE `?` CHANGE `make` `make` VARCHAR(50)", array(LYCHEE_TABLE_PHOTOS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Reset sorting -$query = Database::prepare($database, "UPDATE ? SET value = 'ORDER BY takestamp DESC' WHERE `key` = 'sorting' AND `value` LIKE '%UNIX_TIMESTAMP%'", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} - -# Set version -if (Database::setVersion($database, '020500')===false) return false; - -?> \ No newline at end of file diff --git a/php/database/update_020505.php b/php/database/update_020505.php deleted file mode 100644 index 6c25fa0..0000000 --- a/php/database/update_020505.php +++ /dev/null @@ -1,24 +0,0 @@ -query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `checksum` VARCHAR(100) DEFAULT NULL", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020505', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Set version -if (Database::setVersion($database, '020505')===false) return false; - -?> \ No newline at end of file diff --git a/php/database/update_020601.php b/php/database/update_020601.php deleted file mode 100644 index 453114b..0000000 --- a/php/database/update_020601.php +++ /dev/null @@ -1,24 +0,0 @@ -query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `downloadable` TINYINT(1) NOT NULL DEFAULT 1", array(LYCHEE_TABLE_ALBUMS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020601', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Set version -if (Database::setVersion($database, '020601')===false) return false; - -?> \ No newline at end of file diff --git a/php/database/update_020602.php b/php/database/update_020602.php deleted file mode 100644 index a97f263..0000000 --- a/php/database/update_020602.php +++ /dev/null @@ -1,47 +0,0 @@ -query($query); -if (!$result) { - Log::error($database, 'update_020602', __LINE__, 'Could not find photos without checksum (' . $database->error . ')'); - return false; -} -while ($photo = $result->fetch_object()) { - $checksum = sha1_file(LYCHEE_UPLOADS_BIG . $photo->url); - if ($checksum!==false) { - $query = Database::prepare($database, "UPDATE `?` SET `checksum` = '?' WHERE `id` = '?'", array(LYCHEE_TABLE_PHOTOS, $checksum, $photo->id)); - $setChecksum = $database->query($query); - if (!$setChecksum) { - Log::error($database, 'update_020602', __LINE__, 'Could not update checksum (' . $database->error . ')'); - return false; - } - } else { - Log::error($database, 'update_020602', __LINE__, 'Could not calculate checksum for photo with id ' . $photo->id); - return false; - } -} - -# Add Imagick -$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'imagick' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('imagick', '1')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020602', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } -} - -# Set version -if (Database::setVersion($database, '020602')===false) return false; - -?> \ No newline at end of file diff --git a/php/database/update_020700.php b/php/database/update_020700.php index e6e594e..5575d46 100644 --- a/php/database/update_020700.php +++ b/php/database/update_020700.php @@ -1,43 +1,37 @@ query($query)) { - $query = Database::prepare($database, "ALTER TABLE `?` ADD `medium` TINYINT(1) NOT NULL DEFAULT 0", array(LYCHEE_TABLE_PHOTOS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020700', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } } -# Create medium folder +// Create medium folder if (is_dir(LYCHEE_UPLOADS_MEDIUM)===false) { - # Only create the folder when it is missing - if (@mkdir(LYCHEE_UPLOADS_MEDIUM)===false) - Log::error($database, 'update_020700', __LINE__, 'Could not create medium-folder'); -} -# Add medium to settings -$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'medium' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('medium', '1')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_020700', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; + // Only create the folder when it is missing + if (@mkdir(LYCHEE_UPLOADS_MEDIUM)===false) { + Log::error($connection, 'update_020700', __LINE__, 'Could not create medium-folder'); + Response::error('Could not create medium-folder!'); } + } -# Set version -if (Database::setVersion($database, '020700')===false) return false; +// Set version +if (Database::setVersion($connection, '020700')===false) Response::error('Could not update version of database!'); ?> \ No newline at end of file diff --git a/php/database/update_030000.php b/php/database/update_030000.php index 632277d..1832627 100644 --- a/php/database/update_030000.php +++ b/php/database/update_030000.php @@ -1,37 +1,32 @@ query($query); -if (!$resetUsername) { - Log::error($database, 'update_030000', __LINE__, 'Could not reset username (' . $database->error . ')'); - return false; -} -$query = Database::prepare($database, "UPDATE `?` SET `value` = '' WHERE `key` = 'password' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$resetPassword = $database->query($query); -if (!$resetPassword) { - Log::error($database, 'update_030000', __LINE__, 'Could not reset password (' . $database->error . ')'); - return false; -} +// Remove login +// Login now saved as crypt without md5. Legacy code has been removed. +$query = Database::prepare($connection, "UPDATE `?` SET `value` = '' WHERE `key` = 'username' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); +$result = Database::execute($connection, $query, 'update_030000', __LINE__); -# Make public albums private and reset password -# Password now saved as crypt without md5. Legacy code has been removed. -$query = Database::prepare($database, "UPDATE `?` SET `public` = 0, `password` = NULL", array(LYCHEE_TABLE_ALBUMS)); -$resetPublic = $database->query($query); -if (!$resetPublic) { - Log::error($database, 'update_030000', __LINE__, 'Could not reset public albums (' . $database->error . ')'); - return false; -} +if ($result===false) Response::error('Could not reset username in database!'); -# Set version -if (Database::setVersion($database, '030000')===false) return false; +$query = Database::prepare($connection, "UPDATE `?` SET `value` = '' WHERE `key` = 'password' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); +$result = Database::execute($connection, $query, 'update_030000', __LINE__); + +if ($result===false) Response::error('Could not reset password in database!'); + +// Make public albums private and reset password +// Password now saved as crypt without md5. Legacy code has been removed. +$query = Database::prepare($connection, "UPDATE `?` SET `public` = 0, `password` = NULL", array(LYCHEE_TABLE_ALBUMS)); +$result = Database::execute($connection, $query, 'update_030000', __LINE__); + +if ($result===false) Response::error('Could not reset publicity of photos in database!'); + +// Set version +if (Database::setVersion($connection, '030000')===false) Response::error('Could not update version of database!'); ?> \ No newline at end of file diff --git a/php/database/update_030001.php b/php/database/update_030001.php index 3147255..b3a0e25 100644 --- a/php/database/update_030001.php +++ b/php/database/update_030001.php @@ -1,62 +1,62 @@ query($query); -if (!$result) { - Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} +// Change length of photo title +$query = Database::prepare($connection, "ALTER TABLE `?` CHANGE `title` `title` VARCHAR( 100 ) NOT NULL DEFAULT ''", array(LYCHEE_TABLE_PHOTOS)); +$result = Database::execute($connection, $query, 'update_030001', __LINE__); -# Change length of album title -$query = Database::prepare($database, "ALTER TABLE `?` CHANGE `title` `title` VARCHAR( 100 ) NOT NULL DEFAULT ''", array(LYCHEE_TABLE_ALBUMS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} +if ($result===false) Response::error('Could not change length of photo title in database!'); + +// Change length of album title +$query = Database::prepare($connection, "ALTER TABLE `?` CHANGE `title` `title` VARCHAR( 100 ) NOT NULL DEFAULT ''", array(LYCHEE_TABLE_ALBUMS)); +$result = Database::execute($connection, $query, 'update_030001', __LINE__); + +if ($result===false) Response::error('Could not change length of album title in database!'); + +// Add album sorting to settings +$query = Database::prepare($connection, "SELECT `key` FROM `?` WHERE `key` = 'sortingAlbums' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); +$result = Database::execute($connection, $query, 'update_030001', __LINE__); + +if ($result===false) Response::error('Could not get current album sorting from database!'); -# Add album sorting to settings -$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'sortingAlbums' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('sortingAlbums', 'ORDER BY id DESC')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } + + $query = Database::prepare($connection, "INSERT INTO `?` (`key`, `value`) VALUES ('sortingAlbums', 'ORDER BY id DESC')", array(LYCHEE_TABLE_SETTINGS)); + $result = Database::execute($connection, $query, 'update_030001', __LINE__); + + if ($result===false) Response::error('Could not add album sorting to database!'); + } -# Rename sorting to sortingPhotos -$query = Database::prepare($database, "UPDATE ? SET `key` = 'sortingPhotos' WHERE `key` = 'sorting' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); -if (!$result) { - Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; -} +// Rename sorting to sortingPhotos +$query = Database::prepare($connection, "UPDATE ? SET `key` = 'sortingPhotos' WHERE `key` = 'sorting' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); +$result = Database::execute($connection, $query, 'update_030001', __LINE__); + +if ($result===false) Response::error('Could not rename photo sorting row in database!'); + +// Add identifier to settings +$query = Database::prepare($connection, "SELECT `key` FROM `?` WHERE `key` = 'identifier' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); +$result = Database::execute($connection, $query, 'update_030001', __LINE__); + +if ($result===false) Response::error('Could not get current identifier from database!'); -# Add identifier to settings -$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'identifier' LIMIT 1", array(LYCHEE_TABLE_SETTINGS)); -$result = $database->query($query); if ($result->num_rows===0) { - $identifier = md5(microtime(true)); - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('identifier', '?')", array(LYCHEE_TABLE_SETTINGS, $identifier)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_030001', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } + + $identifier = md5(microtime(true)); + $query = Database::prepare($connection, "INSERT INTO `?` (`key`, `value`) VALUES ('identifier', '?')", array(LYCHEE_TABLE_SETTINGS, $identifier)); + $result = Database::execute($connection, $query, 'update_030001', __LINE__); + + if ($result===false) Response::error('Could not add identifier to database!'); + } -# Set version -if (Database::setVersion($database, '030001')===false) return false; +// Set version +if (Database::setVersion($connection, '030001')===false) Response::error('Could not update version of database!'); ?> \ No newline at end of file diff --git a/php/database/update_030003.php b/php/database/update_030003.php index c658bc0..4e73862 100644 --- a/php/database/update_030003.php +++ b/php/database/update_030003.php @@ -1,25 +1,28 @@ query($query); if ($result->num_rows===0) { - $query = Database::prepare($database, "INSERT INTO `?` (`key`, `value`) VALUES ('skipDuplicates', '0')", array(LYCHEE_TABLE_SETTINGS)); - $result = $database->query($query); - if (!$result) { - Log::error($database, 'update_030003', __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } + + $query = Database::prepare($connection, "INSERT INTO `?` (`key`, `value`) VALUES ('skipDuplicates', '0')", array(LYCHEE_TABLE_SETTINGS)); + $result = Database::execute($connection, $query, 'update_030003', __LINE__); + + if ($result===false) Response::error('Could not add skipDuplicates to database!'); + } -# Set version -if (Database::setVersion($database, '030003')===false) return false; +// Set version +if (Database::setVersion($connection, '030003')===false) Response::error('Could not update version of database!'); ?> \ No newline at end of file diff --git a/php/database/update_030100.php b/php/database/update_030100.php new file mode 100644 index 0000000..3e78358 --- /dev/null +++ b/php/database/update_030100.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/php/define.php b/php/define.php index fb045f2..d7bebb7 100644 --- a/php/define.php +++ b/php/define.php @@ -1,19 +1,14 @@ \ No newline at end of file diff --git a/php/helpers/generateID.php b/php/helpers/generateID.php new file mode 100644 index 0000000..82a23c3 --- /dev/null +++ b/php/helpers/generateID.php @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/php/helpers/getExtension.php b/php/helpers/getExtension.php new file mode 100644 index 0000000..2a48ad8 --- /dev/null +++ b/php/helpers/getExtension.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/php/helpers/getGraphHeader.php b/php/helpers/getGraphHeader.php new file mode 100644 index 0000000..77e48a7 --- /dev/null +++ b/php/helpers/getGraphHeader.php @@ -0,0 +1,56 @@ +getPublic('')===false) return false; + + $query = Database::prepare(Database::get(), "SELECT title, description, url, medium FROM ? WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $photoID)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + $row = $result->fetch_object(); + + if ($row===null) { + Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find photo in database'); + return false; + } + + if ($row->medium==='1') $dir = 'medium'; + else $dir = 'big'; + + $parseUrl = parse_url('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); + $url = $parseUrl['scheme'] . '://' . $parseUrl['host'] . $parseUrl['path'] . '?' . $parseUrl['query']; + $picture = $parseUrl['scheme'] . '://' . $parseUrl['host'] . $parseUrl['path'] . '/../uploads/' . $dir . '/' . $row->url; + + $url = htmlentities($url); + $picture = htmlentities($picture); + + $row->title = htmlentities($row->title); + $row->description = htmlentities($row->description); + + $return = ''; + $return .= ''; + $return .= ''; + $return .= ''; + + $return .= ''; + $return .= ''; + $return .= ''; + $return .= ''; + + $return .= ''; + $return .= ''; + $return .= ''; + $return .= ''; + $return .= ''; + + return $return; + +} + +?> \ No newline at end of file diff --git a/php/helpers/getHashedString.php b/php/helpers/getHashedString.php new file mode 100644 index 0000000..3c5f890 --- /dev/null +++ b/php/helpers/getHashedString.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/php/helpers/hasPermissions.php b/php/helpers/hasPermissions.php new file mode 100644 index 0000000..c862436 --- /dev/null +++ b/php/helpers/hasPermissions.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/php/helpers/search.php b/php/helpers/search.php new file mode 100755 index 0000000..4b05324 --- /dev/null +++ b/php/helpers/search.php @@ -0,0 +1,75 @@ + null, + 'albums' => null, + 'hash' => '' + ); + + /** + * Photos + */ + + $query = Database::prepare(Database::get(), "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE title LIKE '%?%' OR description LIKE '%?%' OR tags LIKE '%?%'", array(LYCHEE_TABLE_PHOTOS, $term, $term, $term)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + while($photo = $result->fetch_assoc()) { + + $photo = Photo::prepareData($photo); + $return['photos'][$photo['id']] = $photo; + + } + + /** + * Albums + */ + + $query = Database::prepare(Database::get(), "SELECT id, title, public, sysstamp, password FROM ? WHERE title LIKE '%?%' OR description LIKE '%?%'", array(LYCHEE_TABLE_ALBUMS, $term, $term)); + $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($result===false) return false; + + while($album = $result->fetch_assoc()) { + + // Turn data from the database into a front-end friendly format + $album = Album::prepareData($album); + + // Thumbs + $query = Database::prepare(Database::get(), "SELECT thumbUrl FROM ? WHERE album = '?' " . Settings::get()['sortingPhotos'] . " LIMIT 0, 3", array(LYCHEE_TABLE_PHOTOS, $album['id'])); + $thumbs = Database::execute(Database::get(), $query, __METHOD__, __LINE__); + + if ($thumbs===false) return false; + + // For each thumb + $k = 0; + while ($thumb = $thumbs->fetch_object()) { + $album['thumbs'][$k] = LYCHEE_URL_UPLOADS_THUMB . $thumb->thumbUrl; + $k++; + } + + // Add to return + $return['albums'][$album['id']] = $album; + + } + + // Hash + $return['hash'] = md5(json_encode($return)); + + return $return; + +} + +?> \ No newline at end of file diff --git a/php/index.php b/php/index.php new file mode 100755 index 0000000..4bacbd7 --- /dev/null +++ b/php/index.php @@ -0,0 +1,91 @@ + \ No newline at end of file diff --git a/php/modules/Album.php b/php/modules/Album.php deleted file mode 100644 index 1b8d1c5..0000000 --- a/php/modules/Album.php +++ /dev/null @@ -1,759 +0,0 @@ -database = $database; - $this->plugins = $plugins; - $this->settings = $settings; - $this->albumIDs = $albumIDs; - - return true; - - } - - public function add($title = 'Untitled', $public = 0, $visible = 1) { - - # Check dependencies - self::dependencies(isset($this->database)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Parse - if (strlen($title)>50) $title = substr($title, 0, 50); - - # Database - $sysstamp = time(); - $query = Database::prepare($this->database, "INSERT INTO ? (title, sysstamp, public, visible) VALUES ('?', '?', '?', '?')", array(LYCHEE_TABLE_ALBUMS, $title, $sysstamp, $public, $visible)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return $this->database->insert_id; - - } - - public static function prepareData($data) { - - # This function requires the following album-attributes and turns them - # into a front-end friendly format: id, title, public, sysstamp, password - # Note that some attributes remain unchanged - - # Check dependencies - self::dependencies(isset($data)); - - # Init - $album = null; - - # Set unchanged attributes - $album['id'] = $data['id']; - $album['title'] = $data['title']; - $album['public'] = $data['public']; - - # Additional attributes - # Only part of $album when available - if (isset($data['description'])) $album['description'] = $data['description']; - if (isset($data['visible'])) $album['visible'] = $data['visible']; - if (isset($data['downloadable'])) $album['downloadable'] = $data['downloadable']; - - # Parse date - $album['sysdate'] = date('F Y', $data['sysstamp']); - - # Parse password - $album['password'] = ($data['password']=='' ? '0' : '1'); - - # Parse thumbs or set default value - $album['thumbs'] = (isset($data['thumbs']) ? explode(',', $data['thumbs']) : array()); - - return $album; - - } - - public function get() { - - # Check dependencies - self::dependencies(isset($this->database, $this->settings, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get album information - switch ($this->albumIDs) { - - case 'f': $return['public'] = '0'; - $query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE star = 1 " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - break; - - case 's': $return['public'] = '0'; - $query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE public = 1 " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - break; - - case 'r': $return['public'] = '0'; - $query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - break; - - case '0': $return['public'] = '0'; - $query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = 0 " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - break; - - default: $query = Database::prepare($this->database, "SELECT * FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - $albums = $this->database->query($query); - $return = $albums->fetch_assoc(); - $return = Album::prepareData($return); - $query = Database::prepare($this->database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE album = '?' " . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); - break; - - } - - # Get photos - $photos = $this->database->query($query); - $previousPhotoID = ''; - while ($photo = $photos->fetch_assoc()) { - - # Turn data from the database into a front-end friendly format - $photo = Photo::prepareData($photo); - - # Set previous and next photoID for navigation purposes - $photo['previousPhoto'] = $previousPhotoID; - $photo['nextPhoto'] = ''; - - # Set current photoID as nextPhoto of previous photo - if ($previousPhotoID!=='') $return['content'][$previousPhotoID]['nextPhoto'] = $photo['id']; - $previousPhotoID = $photo['id']; - - # Add to return - $return['content'][$photo['id']] = $photo; - - } - - if ($photos->num_rows===0) { - - # Album empty - $return['content'] = false; - - } else { - - # Enable next and previous for the first and last photo - $lastElement = end($return['content']); - $lastElementId = $lastElement['id']; - $firstElement = reset($return['content']); - $firstElementId = $firstElement['id']; - - if ($lastElementId!==$firstElementId) { - $return['content'][$lastElementId]['nextPhoto'] = $firstElementId; - $return['content'][$firstElementId]['previousPhoto'] = $lastElementId; - } - - } - - $return['id'] = $this->albumIDs; - $return['num'] = $photos->num_rows; - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return $return; - - } - - public function getAll($public) { - - # Check dependencies - self::dependencies(isset($this->database, $this->settings, $public)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Initialize return var - $return = array( - 'smartalbums' => null, - 'albums' => null, - 'num' => 0 - ); - - # Get SmartAlbums - if ($public===false) $return['smartalbums'] = $this->getSmartInfo(); - - # Albums query - if ($public===false) $query = Database::prepare($this->database, 'SELECT id, title, public, sysstamp, password FROM ? ' . $this->settings['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS)); - else $query = Database::prepare($this->database, 'SELECT id, title, public, sysstamp, password FROM ? WHERE public = 1 AND visible <> 0 ' . $this->settings['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS)); - - # Execute query - $albums = $this->database->query($query); - if (!$albums) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not get all albums (' . $this->database->error . ')'); - exit('Error: ' . $this->database->error); - } - - # For each album - while ($album = $albums->fetch_assoc()) { - - # Turn data from the database into a front-end friendly format - $album = Album::prepareData($album); - - # Thumbs - if (($public===true&&$album['password']==='0')|| - ($public===false)) { - - # Execute query - $query = Database::prepare($this->database, "SELECT thumbUrl FROM ? WHERE album = '?' ORDER BY star DESC, " . substr($this->settings['sortingPhotos'], 9) . " LIMIT 3", array(LYCHEE_TABLE_PHOTOS, $album['id'])); - $thumbs = $this->database->query($query); - - # For each thumb - $k = 0; - while ($thumb = $thumbs->fetch_object()) { - $album['thumbs'][$k] = LYCHEE_URL_UPLOADS_THUMB . $thumb->thumbUrl; - $k++; - } - - } - - # Add to return - $return['albums'][] = $album; - - } - - # Num of albums - $return['num'] = $albums->num_rows; - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return $return; - - } - - private function getSmartInfo() { - - # Check dependencies - self::dependencies(isset($this->database, $this->settings)); - - # Initialize return var - $return = array( - 'unsorted' => null, - 'public' => null, - 'starred' => null, - 'recent' => null - ); - - ### - # Unsorted - ### - - $query = Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE album = 0 ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - $unsorted = $this->database->query($query); - $i = 0; - - $return['unsorted'] = array( - 'thumbs' => array(), - 'num' => $unsorted->num_rows - ); - - while($row = $unsorted->fetch_object()) { - if ($i<3) { - $return['unsorted']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row->thumbUrl; - $i++; - } else break; - } - - ### - # Starred - ### - - $query = Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE star = 1 ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - $starred = $this->database->query($query); - $i = 0; - - $return['starred'] = array( - 'thumbs' => array(), - 'num' => $starred->num_rows - ); - - while($row3 = $starred->fetch_object()) { - if ($i<3) { - $return['starred']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row3->thumbUrl; - $i++; - } else break; - } - - ### - # Public - ### - - $query = Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE public = 1 ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - $public = $this->database->query($query); - $i = 0; - - $return['public'] = array( - 'thumbs' => array(), - 'num' => $public->num_rows - ); - - while($row2 = $public->fetch_object()) { - if ($i<3) { - $return['public']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row2->thumbUrl; - $i++; - } else break; - } - - ### - # Recent - ### - - $query = Database::prepare($this->database, 'SELECT thumbUrl FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) ' . $this->settings['sortingPhotos'], array(LYCHEE_TABLE_PHOTOS)); - $recent = $this->database->query($query); - $i = 0; - - $return['recent'] = array( - 'thumbs' => array(), - 'num' => $recent->num_rows - ); - - while($row3 = $recent->fetch_object()) { - if ($i<3) { - $return['recent']['thumbs'][$i] = LYCHEE_URL_UPLOADS_THUMB . $row3->thumbUrl; - $i++; - } else break; - } - - # Return SmartAlbums - return $return; - - } - - public function getArchive() { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Illicit chars - $badChars = array_merge( - array_map('chr', range(0,31)), - array("<", ">", ":", '"', "/", "\\", "|", "?", "*") - ); - - # Photos query - switch($this->albumIDs) { - case 's': - $photos = Database::prepare($this->database, 'SELECT title, url FROM ? WHERE public = 1', array(LYCHEE_TABLE_PHOTOS)); - $zipTitle = 'Public'; - break; - case 'f': - $photos = Database::prepare($this->database, 'SELECT title, url FROM ? WHERE star = 1', array(LYCHEE_TABLE_PHOTOS)); - $zipTitle = 'Starred'; - break; - case 'r': - $photos = Database::prepare($this->database, 'SELECT title, url FROM ? WHERE LEFT(id, 10) >= unix_timestamp(DATE_SUB(NOW(), INTERVAL 1 DAY)) GROUP BY checksum', array(LYCHEE_TABLE_PHOTOS)); - $zipTitle = 'Recent'; - break; - default: - $photos = Database::prepare($this->database, "SELECT title, url FROM ? WHERE album = '?'", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); - $zipTitle = 'Unsorted'; - } - - # Get title from database when album is not a SmartAlbum - if ($this->albumIDs!=0&&is_numeric($this->albumIDs)) { - - $query = Database::prepare($this->database, "SELECT title FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - $album = $this->database->query($query); - - # Error in database query - if (!$album) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - # Fetch object - $album = $album->fetch_object(); - - # Photo not found - if ($album===null) { - Log::error($this->database, __METHOD__, __LINE__, 'Album not found. Cannot start download.'); - return false; - } - - # Set title - $zipTitle = $album->title; - - } - - # Escape title - $zipTitle = str_replace($badChars, '', $zipTitle); - - $filename = LYCHEE_DATA . $zipTitle . '.zip'; - - # Create zip - $zip = new ZipArchive(); - if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not create ZipArchive'); - return false; - } - - # Execute query - $photos = $this->database->query($photos); - - # Check if album empty - if ($photos->num_rows==0) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not create ZipArchive without images'); - return false; - } - - # Parse each path - $files = array(); - while ($photo = $photos->fetch_object()) { - - # Parse url - $photo->url = LYCHEE_UPLOADS_BIG . $photo->url; - - # Parse title - $photo->title = str_replace($badChars, '', $photo->title); - if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled'; - - # Check if readable - if (!@is_readable($photo->url)) continue; - - # Get extension of image - $extension = getExtension($photo->url); - - # Set title for photo - $zipFileName = $zipTitle . '/' . $photo->title . $extension; - - # Check for duplicates - if (!empty($files)) { - $i = 1; - while (in_array($zipFileName, $files)) { - - # Set new title for photo - $zipFileName = $zipTitle . '/' . $photo->title . '-' . $i . $extension; - - $i++; - - } - } - - # Add to array - $files[] = $zipFileName; - - # Add photo to zip - $zip->addFile($photo->url, $zipFileName); - - } - - # Finish zip - $zip->close(); - - # Send zip - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=\"$zipTitle.zip\""); - header("Content-Length: " . filesize($filename)); - readfile($filename); - - # Delete zip - unlink($filename); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return true; - - } - - public function setTitle($title = 'Untitled') { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Execute query - $query = Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $title, $this->albumIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setDescription($description = '') { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Execute query - $query = Database::prepare($this->database, "UPDATE ? SET description = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $description, $this->albumIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function getPublic() { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - if ($this->albumIDs==='0'||$this->albumIDs==='s'||$this->albumIDs==='f') return false; - - # Execute query - $query = Database::prepare($this->database, "SELECT public FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - $albums = $this->database->query($query); - $album = $albums->fetch_object(); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($album->public==1) return true; - return false; - - } - - public function getDownloadable() { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - if ($this->albumIDs==='0'||$this->albumIDs==='s'||$this->albumIDs==='f'||$this->albumIDs==='r') return false; - - # Execute query - $query = Database::prepare($this->database, "SELECT downloadable FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - $albums = $this->database->query($query); - $album = $albums->fetch_object(); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($album->downloadable==1) return true; - return false; - - } - - public function setPublic($public, $password, $visible, $downloadable) { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Convert values - $public = ($public==='1' ? 1 : 0); - $visible = ($visible==='1' ? 1 : 0); - $downloadable = ($downloadable==='1' ? 1 : 0); - - # Set public - $query = Database::prepare($this->database, "UPDATE ? SET public = '?', visible = '?', downloadable = '?', password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $public, $visible, $downloadable, $this->albumIDs)); - $result = $this->database->query($query); - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - # Reset permissions for photos - if ($public===1) { - $query = Database::prepare($this->database, "UPDATE ? SET public = 0 WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); - $result = $this->database->query($query); - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - # Set password - if (isset($password)&&strlen($password)>0) return $this->setPassword($password); - - return true; - - } - - private function setPassword($password) { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - if (strlen($password)>0) { - - # Get hashed password - $password = getHashedString($password); - - # Set hashed password - # Do not prepare $password because it is hashed and save - # Preparing (escaping) the password would destroy the hash - $query = Database::prepare($this->database, "UPDATE ? SET password = '$password' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - - } else { - - # Unset password - $query = Database::prepare($this->database, "UPDATE ? SET password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - - } - - # Execute query - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function checkPassword($password) { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Execute query - $query = Database::prepare($this->database, "SELECT password FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - $albums = $this->database->query($query); - $album = $albums->fetch_object(); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($album->password=='') return true; - else if ($album->password===crypt($password, $album->password)) return true; - return false; - - } - - public function merge() { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Convert to array - $albumIDs = explode(',', $this->albumIDs); - - # Get first albumID - $albumID = array_splice($albumIDs, 0, 1); - $albumID = $albumID[0]; - - $query = Database::prepare($this->database, "UPDATE ? SET album = ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->albumIDs)); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - # $albumIDs contains all IDs without the first albumID - # Convert to string - $filteredIDs = implode(',', $albumIDs); - - $query = Database::prepare($this->database, "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $filteredIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function delete() { - - # Check dependencies - self::dependencies(isset($this->database, $this->albumIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Init vars - $error = false; - - # Execute query - $query = Database::prepare($this->database, "SELECT id FROM ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); - $photos = $this->database->query($query); - - # For each album delete photo - while ($row = $photos->fetch_object()) { - - $photo = new Photo($this->database, $this->plugins, null, $row->id); - if (!$photo->delete($row->id)) $error = true; - - } - - # Delete albums - $query = Database::prepare($this->database, "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($error) return false; - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - -} - -?> diff --git a/php/modules/Database.php b/php/modules/Database.php deleted file mode 100755 index d7764e4..0000000 --- a/php/modules/Database.php +++ /dev/null @@ -1,338 +0,0 @@ -connect_errno) exit('Error: ' . $database->connect_error); - - # Avoid sql injection on older MySQL versions by using GBK - if ($database->server_version<50500) @$database->set_charset('GBK'); - else @$database->set_charset('utf8'); - - # Set unicode - $database->query('SET NAMES utf8;'); - - # Check database - if (!$database->select_db($name)) - if (!Database::createDatabase($database, $name)) exit('Error: Could not create database!'); - - # Check tables - $query = Database::prepare($database, 'SELECT * FROM ?, ?, ?, ? LIMIT 0', array(LYCHEE_TABLE_PHOTOS, LYCHEE_TABLE_ALBUMS, LYCHEE_TABLE_SETTINGS, LYCHEE_TABLE_LOG)); - if (!$database->query($query)) - if (!Database::createTables($database)) exit('Error: Could not create tables!'); - - return $database; - - } - - static function update($database, $dbName, $version = 0) { - - # Check dependencies - Module::dependencies(isset($database, $dbName)); - if (!isset($version)) return true; - - # List of updates - $updates = array( - '020100', #2.1 - '020101', #2.1.1 - '020200', #2.2 - '020500', #2.5 - '020505', #2.5.5 - '020601', #2.6.1 - '020602', #2.6.2 - '020700', #2.7.0 - '030000', #3.0.0 - '030001', #3.0.1 - '030003' #3.0.3 - ); - - # For each update - foreach ($updates as $update) { - - if ($update<=$version) continue; - - # Load update - include(__DIR__ . '/../database/update_' . $update . '.php'); - - } - - return true; - - } - - static function createConfig($host = 'localhost', $user, $password, $name = 'lychee', $prefix = '') { - - # Check dependencies - Module::dependencies(isset($host, $user, $password, $name)); - - $database = new mysqli($host, $user, $password); - - if ($database->connect_errno) return 'Warning: Connection failed!'; - - # Check if database exists - if (!$database->select_db($name)) { - - # Database doesn't exist - # Check if user can create the database - $result = Database::createDatabase($database, $name); - if ($result===false) return 'Warning: Creation failed!'; - - } - - # Escape data - $host = mysqli_real_escape_string($database, $host); - $user = mysqli_real_escape_string($database, $user); - $password = mysqli_real_escape_string($database, $password); - $name = mysqli_real_escape_string($database, $name); - $prefix = mysqli_real_escape_string($database, $prefix); - - # Save config.php -$config = ""; - - # Save file - if (file_put_contents(LYCHEE_CONFIG_FILE, $config)===false) return 'Warning: Could not create file!'; - - return true; - - } - - static function createDatabase($database, $name = 'lychee') { - - # Check dependencies - Module::dependencies(isset($database, $name)); - - # Create database - $query = Database::prepare($database, 'CREATE DATABASE IF NOT EXISTS ?', array($name)); - $result = $database->query($query); - - if (!$database->select_db($name)||!$result) return false; - return true; - - } - - static function createTables($database) { - - # Check dependencies - Module::dependencies(isset($database)); - - # Create log - $exist = Database::prepare($database, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_LOG)); - if (!$database->query($exist)) { - - # Read file - $file = __DIR__ . '/../database/log_table.sql'; - $query = @file_get_contents($file); - - if (!isset($query)||$query===false) return false; - - # Create table - $query = Database::prepare($database, $query, array(LYCHEE_TABLE_LOG)); - if (!$database->query($query)) return false; - - } - - # Create settings - $exist = Database::prepare($database, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_SETTINGS)); - if (!$database->query($exist)) { - - # Read file - $file = __DIR__ . '/../database/settings_table.sql'; - $query = @file_get_contents($file); - - if (!isset($query)||$query===false) { - Log::error($database, __METHOD__, __LINE__, 'Could not load query for lychee_settings'); - return false; - } - - # Create table - $query = Database::prepare($database, $query, array(LYCHEE_TABLE_SETTINGS)); - if (!$database->query($query)) { - Log::error($database, __METHOD__, __LINE__, $database->error); - return false; - } - - # Read file - $file = __DIR__ . '/../database/settings_content.sql'; - $query = @file_get_contents($file); - - if (!isset($query)||$query===false) { - Log::error($database, __METHOD__, __LINE__, 'Could not load content-query for lychee_settings'); - return false; - } - - # Add content - $query = Database::prepare($database, $query, array(LYCHEE_TABLE_SETTINGS)); - if (!$database->query($query)) { - Log::error($database, __METHOD__, __LINE__, $database->error); - return false; - } - - # Generate identifier - $identifier = md5(microtime(true)); - $query = Database::prepare($database, "UPDATE `?` SET `value` = '?' WHERE `key` = 'identifier' LIMIT 1", array(LYCHEE_TABLE_SETTINGS, $identifier)); - if (!$database->query($query)) { - Log::error($database, __METHOD__, __LINE__, $database->error); - return false; - } - - } - - # Create albums - $exist = Database::prepare($database, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_ALBUMS)); - if (!$database->query($exist)) { - - # Read file - $file = __DIR__ . '/../database/albums_table.sql'; - $query = @file_get_contents($file); - - if (!isset($query)||$query===false) { - Log::error($database, __METHOD__, __LINE__, 'Could not load query for lychee_albums'); - return false; - } - - # Create table - $query = Database::prepare($database, $query, array(LYCHEE_TABLE_ALBUMS)); - if (!$database->query($query)) { - Log::error($database, __METHOD__, __LINE__, $database->error); - return false; - } - - } - - # Create photos - $exist = Database::prepare($database, 'SELECT * FROM ? LIMIT 0', array(LYCHEE_TABLE_PHOTOS)); - if (!$database->query($exist)) { - - # Read file - $file = __DIR__ . '/../database/photos_table.sql'; - $query = @file_get_contents($file); - - if (!isset($query)||$query===false) { - Log::error($database, __METHOD__, __LINE__, 'Could not load query for lychee_photos'); - return false; - } - - # Create table - $query = Database::prepare($database, $query, array(LYCHEE_TABLE_PHOTOS)); - if (!$database->query($query)) { - Log::error($database, __METHOD__, __LINE__, $database->error); - return false; - } - - } - - return true; - - } - - static function setVersion($database, $version) { - - $query = Database::prepare($database, "UPDATE ? SET value = '?' WHERE `key` = 'version'", array(LYCHEE_TABLE_SETTINGS, $version)); - $result = $database->query($query); - if (!$result) { - Log::error($database, __METHOD__, __LINE__, 'Could not update database (' . $database->error . ')'); - return false; - } - - } - - static function prepare($database, $query, $data) { - - # Check dependencies - Module::dependencies(isset($database, $query, $data)); - - # Count the number of placeholders and compare it with the number of arguments - # If it doesn't match, calculate the difference and skip this number of placeholders before starting the replacement - # This avoids problems with placeholders in user-input - # $skip = Number of placeholders which need to be skipped - $skip = 0; - $temp = ''; - $num = array( - 'placeholder' => substr_count($query, '?'), - 'data' => count($data) - ); - - if (($num['data']-$num['placeholder'])<0) Log::notice($database, __METHOD__, __LINE__, 'Could not completely prepare query. Query has more placeholders than values.'); - - foreach ($data as $value) { - - # Escape - $value = mysqli_real_escape_string($database, $value); - - # Recalculate number of placeholders - $num['placeholder'] = substr_count($query, '?'); - - # Calculate number of skips - if ($num['placeholder']>$num['data']) $skip = $num['placeholder'] - $num['data']; - - if ($skip>0) { - - # Need to skip $skip placeholders, because the user input contained placeholders - # Calculate a substring which does not contain the user placeholders - # 1 or -1 is the length of the placeholder (placeholder = ?) - - $pos = -1; - for ($i=$skip; $i>0; $i--) $pos = strpos($query, '?', $pos + 1); - $pos++; - - $temp = substr($query, 0, $pos); # First part of $query - $query = substr($query, $pos); # Last part of $query - - } - - # Replace - $query = preg_replace('/\?/', $value, $query, 1); - - if ($skip>0) { - - # Reassemble the parts of $query - $query = $temp . $query; - - } - - # Reset skip - $skip = 0; - - # Decrease number of data elements - $num['data']--; - - } - - return $query; - - } - -} - -?> diff --git a/php/modules/Import.php b/php/modules/Import.php deleted file mode 100644 index 621c562..0000000 --- a/php/modules/Import.php +++ /dev/null @@ -1,211 +0,0 @@ -database = $database; - $this->plugins = $plugins; - $this->settings = $settings; - - return true; - - } - - private function photo($path, $albumID = 0, $description = '', $tags = '') { - - # Check dependencies - self::dependencies(isset($this->database, $this->plugins, $this->settings, $path)); - - # No need to validate photo type and extension in this function. - # $photo->add will take care of it. - - $info = getimagesize($path); - $size = filesize($path); - $photo = new Photo($this->database, $this->plugins, $this->settings, null); - - $nameFile = array(array()); - $nameFile[0]['name'] = $path; - $nameFile[0]['type'] = $info['mime']; - $nameFile[0]['tmp_name'] = $path; - $nameFile[0]['error'] = 0; - $nameFile[0]['size'] = $size; - $nameFile[0]['error'] = UPLOAD_ERR_OK; - - if (!$photo->add($nameFile, $albumID, $description, $tags, true)) return false; - return true; - - } - - public function url($urls, $albumID = 0) { - - # Check dependencies - self::dependencies(isset($this->database, $urls)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - $error = false; - - # Parse URLs - $urls = str_replace(' ', '%20', $urls); - $urls = explode(',', $urls); - - foreach ($urls as &$url) { - - # Validate photo type and extension even when $this->photo (=> $photo->add) will do the same. - # This prevents us from downloading invalid photos. - - # Verify extension - $extension = getExtension($url); - if (!in_array(strtolower($extension), Photo::$validExtensions, true)) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Photo format not supported (' . $url . ')'); - continue; - } - - # Verify image - $type = @exif_imagetype($url); - if (!in_array($type, Photo::$validTypes, true)) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Photo type not supported (' . $url . ')'); - continue; - } - - $pathinfo = pathinfo($url); - $filename = $pathinfo['filename'] . '.' . $pathinfo['extension']; - $tmp_name = LYCHEE_DATA . $filename; - - if (@copy($url, $tmp_name)===false) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Could not copy file (' . $tmp_name . ') to temp-folder (' . $tmp_name . ')'); - continue; - } - - # Import photo - if (!$this->photo($tmp_name, $albumID)) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Could not import file: ' . $tmp_name); - continue; - } - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($error===false) return true; - return false; - - } - - public function server($path, $albumID = 0) { - - # Check dependencies - self::dependencies(isset($this->database, $this->plugins, $this->settings)); - - # Parse path - if (!isset($path)) $path = LYCHEE_UPLOADS_IMPORT; - if (substr($path, -1)==='/') $path = substr($path, 0, -1); - - if (is_dir($path)===false) { - Log::error($this->database, __METHOD__, __LINE__, 'Given path is not a directory (' . $path . ')'); - return 'Error: Given path is not a directory!'; - } - - # Skip folders of Lychee - if ($path===LYCHEE_UPLOADS_BIG||($path . '/')===LYCHEE_UPLOADS_BIG|| - $path===LYCHEE_UPLOADS_MEDIUM||($path . '/')===LYCHEE_UPLOADS_MEDIUM|| - $path===LYCHEE_UPLOADS_THUMB||($path . '/')===LYCHEE_UPLOADS_THUMB) { - Log::error($this->database, __METHOD__, __LINE__, 'The given path is a reserved path of Lychee (' . $path . ')'); - return 'Error: Given path is a reserved path of Lychee!'; - } - - $error = false; - $contains['photos'] = false; - $contains['albums'] = false; - - # Call plugins - # Note that updated albumId and path explicitly passed, rather - # than using func_get_args() which will only return original ones - $this->plugins(__METHOD__, 0, array($albumID, $path)); - - # Get all files - $files = glob($path . '/*'); - - foreach ($files as $file) { - - # It is possible to move a file because of directory permissions but - # the file may still be unreadable by the user - if (!is_readable($file)) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Could not read file or directory: ' . $file); - continue; - } - - if (@exif_imagetype($file)!==false) { - - # Photo - - $contains['photos'] = true; - - if (!$this->photo($file, $albumID)) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Could not import file: ' . $file); - continue; - } - - } else if (is_dir($file)) { - - # Folder - - $album = new Album($this->database, $this->plugins, $this->settings, null); - $newAlbumID = $album->add('[Import] ' . basename($file)); - $contains['albums'] = true; - - if ($newAlbumID===false) { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Could not create album in Lychee (' . $newAlbumID . ')'); - continue; - } - - $import = $this->server($file . '/', $newAlbumID); - - if ($import!==true&&$import!=='Notice: Import only contains albums!') { - $error = true; - Log::error($this->database, __METHOD__, __LINE__, 'Could not import folder. Function returned warning.'); - continue; - } - - } - - } - - # Call plugins - # Note that updated albumId and path explicitly passed, rather - # than using func_get_args() which will only return original ones - $this->plugins(__METHOD__, 1, array($albumID, $path)); - - # The following returns will be caught in the front-end - if ($contains['photos']===false&&$contains['albums']===false) return 'Warning: Folder empty or no readable files to process!'; - if ($contains['photos']===false&&$contains['albums']===true) return 'Notice: Import only contained albums!'; - - if ($error===true) return false; - return true; - - } - -} - -?> diff --git a/php/modules/Log.php b/php/modules/Log.php deleted file mode 100644 index 1725020..0000000 --- a/php/modules/Log.php +++ /dev/null @@ -1,49 +0,0 @@ -query($query); - - if (!$result) return false; - return true; - - } - -} - -?> \ No newline at end of file diff --git a/php/modules/Module.php b/php/modules/Module.php deleted file mode 100644 index 3e8b672..0000000 --- a/php/modules/Module.php +++ /dev/null @@ -1,36 +0,0 @@ -plugins, $name, $location, $args)) return false; - - # Parse - $location = ($location===0 ? 'before' : 'after'); - - # Call plugins - $this->plugins->activate($name . ":" . $location, $args); - - return true; - - } - - public static function dependencies($available = false) { - - if ($available===false) exit('Error: Can not execute function. Missing parameters or variables.'); - - } - -} - -?> \ No newline at end of file diff --git a/php/modules/Photo.php b/php/modules/Photo.php deleted file mode 100755 index bc745dd..0000000 --- a/php/modules/Photo.php +++ /dev/null @@ -1,1249 +0,0 @@ -database = $database; - $this->plugins = $plugins; - $this->settings = $settings; - $this->photoIDs = $photoIDs; - - return true; - - } - - public function add($files, $albumID = 0, $description = '', $tags = '', $returnOnError = false) { - - # Use $returnOnError if you want to handle errors by your own - # e.g. when calling this functions inside an if-condition - - # Check dependencies - self::dependencies(isset($this->database, $this->settings, $files)); - - # Check permissions - if (hasPermissions(LYCHEE_UPLOADS)===false|| - hasPermissions(LYCHEE_UPLOADS_BIG)===false|| - hasPermissions(LYCHEE_UPLOADS_THUMB)===false) { - Log::error($this->database, __METHOD__, __LINE__, 'An upload-folder is missing or not readable and writable'); - exit('Error: An upload-folder is missing or not readable and writable!'); - } - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - switch($albumID) { - - case 's': - # s for public (share) - $public = 1; - $star = 0; - $albumID = 0; - break; - - case 'f': - # f for starred (fav) - $star = 1; - $public = 0; - $albumID = 0; - break; - - case 'r': - # r for recent - $public = 0; - $star = 0; - $albumID = 0; - break; - - default: - $star = 0; - $public = 0; - break; - - } - - foreach ($files as $file) { - - # Check if file exceeds the upload_max_filesize directive - if ($file['error']===UPLOAD_ERR_INI_SIZE) { - Log::error($this->database, __METHOD__, __LINE__, 'The uploaded file exceeds the upload_max_filesize directive in php.ini'); - if ($returnOnError===true) return false; - exit('Error: The uploaded file exceeds the upload_max_filesize directive in php.ini!'); - } - - # Check if file was only partially uploaded - if ($file['error']===UPLOAD_ERR_PARTIAL) { - Log::error($this->database, __METHOD__, __LINE__, 'The uploaded file was only partially uploaded'); - if ($returnOnError===true) return false; - exit('Error: The uploaded file was only partially uploaded!'); - } - - # Check if writing file to disk failed - if ($file['error']===UPLOAD_ERR_CANT_WRITE) { - Log::error($this->database, __METHOD__, __LINE__, 'Failed to write photo to disk'); - if ($returnOnError===true) return false; - exit('Error: Failed to write photo to disk!'); - } - - # Check if a extension stopped the file upload - if ($file['error']===UPLOAD_ERR_EXTENSION) { - Log::error($this->database, __METHOD__, __LINE__, 'A PHP extension stopped the file upload'); - if ($returnOnError===true) return false; - exit('Error: A PHP extension stopped the file upload!'); - } - - # Check if the upload was successful - if ($file['error']!==UPLOAD_ERR_OK) { - Log::error($this->database, __METHOD__, __LINE__, 'Upload contains an error (' . $file['error'] . ')'); - if ($returnOnError===true) return false; - exit('Error: Upload failed!'); - } - - # Verify extension - $extension = getExtension($file['name']); - if (!in_array(strtolower($extension), Photo::$validExtensions, true)) { - Log::error($this->database, __METHOD__, __LINE__, 'Photo format not supported'); - if ($returnOnError===true) return false; - exit('Error: Photo format not supported!'); - } - - # Verify image - $type = @exif_imagetype($file['tmp_name']); - if (!in_array($type, Photo::$validTypes, true)) { - Log::error($this->database, __METHOD__, __LINE__, 'Photo type not supported'); - if ($returnOnError===true) return false; - exit('Error: Photo type not supported!'); - } - - # Generate id - $id = str_replace('.', '', microtime(true)); - while(strlen($id)<14) $id .= 0; - - # Set paths - $tmp_name = $file['tmp_name']; - $photo_name = md5($id) . $extension; - $path = LYCHEE_UPLOADS_BIG . $photo_name; - - # Calculate checksum - $checksum = sha1_file($tmp_name); - if ($checksum===false) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not calculate checksum for photo'); - if ($returnOnError===true) return false; - exit('Error: Could not calculate checksum for photo!'); - } - - # Check if image exists based on checksum - if ($checksum===false) { - - $checksum = ''; - $exists = false; - - } else { - - $exists = $this->exists($checksum); - - if ($exists!==false) { - $photo_name = $exists['photo_name']; - $path = $exists['path']; - $path_thumb = $exists['path_thumb']; - $medium = ($exists['medium']==='1' ? 1 : 0); - $exists = true; - } - - } - - if ($exists===false) { - - # Import if not uploaded via web - if (!is_uploaded_file($tmp_name)) { - if (!@copy($tmp_name, $path)) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not copy photo to uploads'); - if ($returnOnError===true) return false; - exit('Error: Could not copy photo to uploads!'); - } else @unlink($tmp_name); - } else { - if (!@move_uploaded_file($tmp_name, $path)) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not move photo to uploads'); - if ($returnOnError===true) return false; - exit('Error: Could not move photo to uploads!'); - } - } - - } else { - - # Photo already exists - # Check if the user wants to skip duplicates - if ($this->settings['skipDuplicates']==='1') { - Log::notice($this->database, __METHOD__, __LINE__, 'Skipped upload of existing photo because skipDuplicates is activated'); - if ($returnOnError===true) return false; - exit('Warning: This photo has been skipped because it\'s already in your library.'); - } - - } - - # Read infos - $info = $this->getInfo($path); - - # Use title of file if IPTC title missing - if ($info['title']==='') $info['title'] = substr(basename($file['name'], $extension), 0, 30); - - # Use description parameter if set - if ($description==='') $description = $info['description']; - - if ($exists===false) { - - # Set orientation based on EXIF data - if ($file['type']==='image/jpeg'&&isset($info['orientation'])&&$info['orientation']!=='') { - $adjustFile = $this->adjustFile($path, $info); - if ($adjustFile!==false) $info = $adjustFile; - else Log::notice($this->database, __METHOD__, __LINE__, 'Skipped adjustment of photo (' . $info['title'] . ')'); - } - - # Set original date - if ($info['takestamp']!==''&&$info['takestamp']!==0) @touch($path, $info['takestamp']); - - # Create Thumb - if (!$this->createThumb($path, $photo_name, $info['type'], $info['width'], $info['height'])) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not create thumbnail for photo'); - if ($returnOnError===true) return false; - exit('Error: Could not create thumbnail for photo!'); - } - - # Create Medium - if ($this->createMedium($path, $photo_name, $info['width'], $info['height'])) $medium = 1; - else $medium = 0; - - # Set thumb url - $path_thumb = md5($id) . '.jpeg'; - - } - - # Save to DB - $values = array(LYCHEE_TABLE_PHOTOS, $id, $info['title'], $photo_name, $description, $tags, $info['type'], $info['width'], $info['height'], $info['size'], $info['iso'], $info['aperture'], $info['make'], $info['model'], $info['shutter'], $info['focal'], $info['takestamp'], $path_thumb, $albumID, $public, $star, $checksum, $medium); - $query = Database::prepare($this->database, "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum, medium) VALUES ('?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?')", $values); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - if ($returnOnError===true) return false; - exit('Error: Could not save photo in database!'); - } - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return true; - - } - - private function exists($checksum, $photoID = null) { - - # Check dependencies - self::dependencies(isset($this->database, $checksum)); - - # Exclude $photoID from select when $photoID is set - if (isset($photoID)) $query = Database::prepare($this->database, "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' AND id <> '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum, $photoID)); - else $query = Database::prepare($this->database, "SELECT id, url, thumbUrl, medium FROM ? WHERE checksum = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $checksum)); - - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not check for existing photos with the same checksum'); - return false; - } - - if ($result->num_rows===1) { - - $result = $result->fetch_object(); - - $return = array( - 'photo_name' => $result->url, - 'path' => LYCHEE_UPLOADS_BIG . $result->url, - 'path_thumb' => $result->thumbUrl, - 'medium' => $result->medium - ); - - return $return; - - } - - return false; - - } - - private function createThumb($url, $filename, $type, $width, $height) { - - # Check dependencies - self::dependencies(isset($this->database, $this->settings, $url, $filename, $type, $width, $height)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Size of the thumbnail - $newWidth = 200; - $newHeight = 200; - - $photoName = explode('.', $filename); - $newUrl = LYCHEE_UPLOADS_THUMB . $photoName[0] . '.jpeg'; - $newUrl2x = LYCHEE_UPLOADS_THUMB . $photoName[0] . '@2x.jpeg'; - - # Create thumbnails with Imagick - if(extension_loaded('imagick')&&$this->settings['imagick']==='1') { - - # Read image - $thumb = new Imagick(); - $thumb->readImage($url); - $thumb->setImageCompressionQuality($this->settings['thumbQuality']); - $thumb->setImageFormat('jpeg'); - - # Copy image for 2nd thumb version - $thumb2x = clone $thumb; - - # Create 1st version - $thumb->cropThumbnailImage($newWidth, $newHeight); - $thumb->writeImage($newUrl); - $thumb->clear(); - $thumb->destroy(); - - # Create 2nd version - $thumb2x->cropThumbnailImage($newWidth*2, $newHeight*2); - $thumb2x->writeImage($newUrl2x); - $thumb2x->clear(); - $thumb2x->destroy(); - - } else { - - # Create image - $thumb = imagecreatetruecolor($newWidth, $newHeight); - $thumb2x = imagecreatetruecolor($newWidth*2, $newHeight*2); - - # Set position - if ($width<$height) { - $newSize = $width; - $startWidth = 0; - $startHeight = $height/2 - $width/2; - } else { - $newSize = $height; - $startWidth = $width/2 - $height/2; - $startHeight = 0; - } - - # Create new image - switch($type) { - case 'image/jpeg': $sourceImg = imagecreatefromjpeg($url); break; - case 'image/png': $sourceImg = imagecreatefrompng($url); break; - case 'image/gif': $sourceImg = imagecreatefromgif($url); break; - default: Log::error($this->database, __METHOD__, __LINE__, 'Type of photo is not supported'); - return false; - break; - } - - # Create thumb - fastimagecopyresampled($thumb, $sourceImg, 0, 0, $startWidth, $startHeight, $newWidth, $newHeight, $newSize, $newSize); - imagejpeg($thumb, $newUrl, $this->settings['thumbQuality']); - imagedestroy($thumb); - - # Create retina thumb - fastimagecopyresampled($thumb2x, $sourceImg, 0, 0, $startWidth, $startHeight, $newWidth*2, $newHeight*2, $newSize, $newSize); - imagejpeg($thumb2x, $newUrl2x, $this->settings['thumbQuality']); - imagedestroy($thumb2x); - - # Free memory - imagedestroy($sourceImg); - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return true; - - } - - private function createMedium($url, $filename, $width, $height) { - - # Function creates a smaller version of a photo when its size is bigger than a preset size - # Excepts the following: - # (string) $url = Path to the photo-file - # (string) $filename = Name of the photo-file - # (int) $width = Width of the photo - # (int) $height = Height of the photo - # Returns the following - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->settings, $url, $filename, $width, $height)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Set to true when creation of medium-photo failed - $error = false; - - # Size of the medium-photo - # When changing these values, - # also change the size detection in the front-end - $newWidth = 1920; - $newHeight = 1080; - - # Check permissions - if (hasPermissions(LYCHEE_UPLOADS_MEDIUM)===false) { - - # Permissions are missing - Log::notice($this->database, __METHOD__, __LINE__, 'Skipped creation of medium-photo, because uploads/medium/ is missing or not readable and writable.'); - $error = true; - - } - - # Is photo big enough? - # Is medium activated? - # Is Imagick installed and activated? - if (($error===false)&& - ($width>$newWidth||$height>$newHeight)&& - ($this->settings['medium']==='1')&& - (extension_loaded('imagick')&&$this->settings['imagick']==='1')) { - - $newUrl = LYCHEE_UPLOADS_MEDIUM . $filename; - - # Read image - $medium = new Imagick(); - $medium->readImage($url); - - # Adjust image - $medium->scaleImage($newWidth, $newHeight, true); - - # Save image - try { $medium->writeImage($newUrl); } - catch (ImagickException $err) { - Log::notice($this->database, __METHOD__, __LINE__, 'Could not save medium-photo: ' . $err->getMessage()); - $error = true; - } - - $medium->clear(); - $medium->destroy(); - - } else { - - # Photo too small or - # Medium is deactivated or - # Imagick not installed - $error = true; - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($error===true) return false; - return true; - - } - - public function adjustFile($path, $info) { - - # Function rotates and flips a photo based on its EXIF orientation - # Excepts the following: - # (string) $path = Path to the photo-file - # (array) $info = ['orientation', 'width', 'height'] - # Returns the following - # (array) $info = ['orientation', 'width', 'height'] = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($path, $info)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - $swapSize = false; - - if (extension_loaded('imagick')&&$this->settings['imagick']==='1') { - - switch ($info['orientation']) { - - case 3: - $rotateImage = 180; - break; - - case 6: - $rotateImage = 90; - $swapSize = true; - break; - - case 8: - $rotateImage = 270; - $swapSize = true; - break; - - default: - return false; - break; - - } - - if ($rotateImage!==0) { - $image = new Imagick(); - $image->readImage($path); - $image->rotateImage(new ImagickPixel(), $rotateImage); - $image->setImageOrientation(1); - $image->writeImage($path); - $image->clear(); - $image->destroy(); - } - - } else { - - $newWidth = $info['width']; - $newHeight = $info['height']; - $sourceImg = imagecreatefromjpeg($path); - - switch ($info['orientation']) { - - case 2: - # mirror - # not yet implemented - return false; - break; - - case 3: - $sourceImg = imagerotate($sourceImg, -180, 0); - break; - - case 4: - # rotate 180 and mirror - # not yet implemented - return false; - break; - - case 5: - # rotate 90 and mirror - # not yet implemented - return false; - break; - - case 6: - $sourceImg = imagerotate($sourceImg, -90, 0); - $newWidth = $info['height']; - $newHeight = $info['width']; - $swapSize = true; - break; - - case 7: - # rotate -90 and mirror - # not yet implemented - return false; - break; - - case 8: - $sourceImg = imagerotate($sourceImg, 90, 0); - $newWidth = $info['height']; - $newHeight = $info['width']; - $swapSize = true; - break; - - default: - return false; - break; - - } - - # Recreate photo - $newSourceImg = imagecreatetruecolor($newWidth, $newHeight); - imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight); - imagejpeg($newSourceImg, $path, 100); - - # Free memory - imagedestroy($sourceImg); - imagedestroy($newSourceImg); - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - # SwapSize should be true when the image has been rotated - # Return new dimensions in this case - if ($swapSize===true) { - $swapSize = $info['width']; - $info['width'] = $info['height']; - $info['height'] = $swapSize; - } - - return $info; - - } - - public static function prepareData($data) { - - # Function turns photo-attributes into a front-end friendly format. Note that some attributes remain unchanged. - # Excepts the following: - # (array) $data = ['id', 'title', 'tags', 'public', 'star', 'album', 'thumbUrl', 'takestamp', 'url'] - # Returns the following: - # (array) $photo - - # Check dependencies - self::dependencies(isset($data)); - - # Init - $photo = null; - - # Set unchanged attributes - $photo['id'] = $data['id']; - $photo['title'] = $data['title']; - $photo['tags'] = $data['tags']; - $photo['public'] = $data['public']; - $photo['star'] = $data['star']; - $photo['album'] = $data['album']; - - # Parse urls - $photo['thumbUrl'] = LYCHEE_URL_UPLOADS_THUMB . $data['thumbUrl']; - $photo['url'] = LYCHEE_URL_UPLOADS_BIG . $data['url']; - - # Use takestamp as sysdate when possible - if (isset($data['takestamp'])&&$data['takestamp']!=='0') { - - # Use takestamp - $photo['cameraDate'] = '1'; - $photo['sysdate'] = date('d F Y', $data['takestamp']); - - } else { - - # Use sysstamp from the id - $photo['cameraDate'] = '0'; - $photo['sysdate'] = date('d F Y', substr($data['id'], 0, -4)); - - } - - return $photo; - - } - - public function get($albumID) { - - # Functions returns data of a photo - # Excepts the following: - # (string) $albumID = Album which is currently visible to the user - # Returns the following: - # (array) $photo - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get photo - $query = Database::prepare($this->database, "SELECT * FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - $photo = $photos->fetch_assoc(); - - # Parse photo - $photo['sysdate'] = date('d M. Y', substr($photo['id'], 0, -4)); - if (strlen($photo['takestamp'])>1) $photo['takedate'] = date('d M. Y', $photo['takestamp']); - - # Parse medium - if ($photo['medium']==='1') $photo['medium'] = LYCHEE_URL_UPLOADS_MEDIUM . $photo['url']; - else $photo['medium'] = ''; - - # Parse paths - $photo['url'] = LYCHEE_URL_UPLOADS_BIG . $photo['url']; - $photo['thumbUrl'] = LYCHEE_URL_UPLOADS_THUMB . $photo['thumbUrl']; - - if ($albumID!='false') { - - # Only show photo as public when parent album is public - # Check if parent album is not 'Unsorted' - if ($photo['album']!=='0') { - - # Get album - $query = Database::prepare($this->database, "SELECT public FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $photo['album'])); - $albums = $this->database->query($query); - $album = $albums->fetch_assoc(); - - # Parse album - $photo['public'] = ($album['public']==='1' ? '2' : $photo['public']); - - } - - $photo['original_album'] = $photo['album']; - $photo['album'] = $albumID; - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return $photo; - - } - - public function getInfo($url) { - - # Functions returns information and metadata of a photo - # Excepts the following: - # (string) $url = Path to photo-file - # Returns the following: - # (array) $return - - # Check dependencies - self::dependencies(isset($this->database, $url)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - $iptcArray = array(); - $info = getimagesize($url, $iptcArray); - - # General information - $return['type'] = $info['mime']; - $return['width'] = $info[0]; - $return['height'] = $info[1]; - - # Size - $size = filesize($url)/1024; - if ($size>=1024) $return['size'] = round($size/1024, 1) . ' MB'; - else $return['size'] = round($size, 1) . ' KB'; - - # IPTC Metadata Fallback - $return['title'] = ''; - $return['description'] = ''; - - # IPTC Metadata - if(isset($iptcArray['APP13'])) { - - $iptcInfo = iptcparse($iptcArray['APP13']); - if (is_array($iptcInfo)) { - - $temp = @$iptcInfo['2#105'][0]; - if (isset($temp)&&strlen($temp)>0) $return['title'] = $temp; - - $temp = @$iptcInfo['2#120'][0]; - if (isset($temp)&&strlen($temp)>0) $return['description'] = $temp; - - $temp = @$iptcInfo['2#005'][0]; - if (isset($temp)&&strlen($temp)>0&&$return['title']==='') $return['title'] = $temp; - - } - - } - - # EXIF Metadata Fallback - $return['orientation'] = ''; - $return['iso'] = ''; - $return['aperture'] = ''; - $return['make'] = ''; - $return['model'] = ''; - $return['shutter'] = ''; - $return['focal'] = ''; - $return['takestamp'] = 0; - - # Read EXIF - if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0); - else $exif = false; - - # EXIF Metadata - if ($exif!==false) { - - if (isset($exif['Orientation'])) $return['orientation'] = $exif['Orientation']; - else if (isset($exif['IFD0']['Orientation'])) $return['orientation'] = $exif['IFD0']['Orientation']; - - $temp = @$exif['ISOSpeedRatings']; - if (isset($temp)) $return['iso'] = $temp; - - $temp = @$exif['COMPUTED']['ApertureFNumber']; - if (isset($temp)) $return['aperture'] = $temp; - - $temp = @$exif['Make']; - if (isset($temp)) $return['make'] = trim($temp); - - $temp = @$exif['Model']; - if (isset($temp)) $return['model'] = trim($temp); - - $temp = @$exif['ExposureTime']; - if (isset($temp)) $return['shutter'] = $exif['ExposureTime'] . ' s'; - - $temp = @$exif['FocalLength']; - if (isset($temp)) { - if (strpos($temp, '/')!==FALSE) { - $temp = explode('/', $temp, 2); - $temp = $temp[0] / $temp[1]; - $temp = round($temp, 1); - $return['focal'] = $temp . ' mm'; - } - $return['focal'] = $temp . ' mm'; - } - - $temp = @$exif['DateTimeOriginal']; - if (isset($temp)) $return['takestamp'] = strtotime($temp); - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return $return; - - } - - public function getArchive() { - - # Functions starts a download of a photo - # Returns the following: - # (boolean + output) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get photo - $query = Database::prepare($this->database, "SELECT title, url FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - $photo = $photos->fetch_object(); - - # Error in database query - if (!$photos) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - # Photo not found - if ($photo===null) { - Log::error($this->database, __METHOD__, __LINE__, 'Album not found. Cannot start download.'); - return false; - } - - # Get extension - $extension = getExtension($photo->url); - if ($extension===false) { - Log::error($this->database, __METHOD__, __LINE__, 'Invalid photo extension'); - return false; - } - - # Illicit chars - $badChars = array_merge( - array_map('chr', range(0,31)), - array("<", ">", ":", '"', "/", "\\", "|", "?", "*") - ); - - # Parse title - if ($photo->title=='') $photo->title = 'Untitled'; - - # Escape title - $photo->title = str_replace($badChars, '', $photo->title); - - # Set headers - header("Content-Type: application/octet-stream"); - header("Content-Disposition: attachment; filename=\"" . $photo->title . $extension . "\""); - header("Content-Length: " . filesize(LYCHEE_UPLOADS_BIG . $photo->url)); - - # Send file - readfile(LYCHEE_UPLOADS_BIG . $photo->url); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return true; - - } - - public function setTitle($title) { - - # Functions sets the title of a photo - # Excepts the following: - # (string) $title = Title with a maximum length of 50 chars - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Set title - $query = Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $title, $this->photoIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setDescription($description) { - - # Functions sets the description of a photo - # Excepts the following: - # (string) $description = Description with a maximum length of 1000 chars - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Set description - $query = Database::prepare($this->database, "UPDATE ? SET description = '?' WHERE id IN ('?')", array(LYCHEE_TABLE_PHOTOS, $description, $this->photoIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setStar() { - - # Functions stars a photo - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Init vars - $error = false; - - # Get photos - $query = Database::prepare($this->database, "SELECT id, star FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - - # For each photo - while ($photo = $photos->fetch_object()) { - - # Invert star - $star = ($photo->star==0 ? 1 : 0); - - # Set star - $query = Database::prepare($this->database, "UPDATE ? SET star = '?' WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $star, $photo->id)); - $star = $this->database->query($query); - if (!$star) $error = true; - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if ($error===true) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function getPublic($password) { - - # Functions checks if photo or parent album is public - # Returns the following: - # (int) 0 = Photo private and parent album private - # (int) 1 = Album public, but password incorrect - # (int) 2 = Photo public or album public and password correct - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get photo - $query = Database::prepare($this->database, "SELECT public, album FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - $photo = $photos->fetch_object(); - - # Check if public - if ($photo->public==='1') { - - # Photo public - return 2; - - } else { - - # Check if album public - $album = new Album($this->database, null, null, $photo->album); - $agP = $album->getPublic(); - $acP = $album->checkPassword($password); - - # Album public and password correct - if ($agP===true&&$acP===true) return 2; - - # Album public, but password incorrect - if ($agP===true&&$acP===false) return 1; - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - # Photo private - return 0; - - } - - public function setPublic() { - - # Functions toggles the public property of a photo - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get public - $query = Database::prepare($this->database, "SELECT public FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - $photo = $photos->fetch_object(); - - # Invert public - $public = ($photo->public==0 ? 1 : 0); - - # Set public - $query = Database::prepare($this->database, "UPDATE ? SET public = '?' WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $public, $this->photoIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - function setAlbum($albumID) { - - # Functions sets the parent album of a photo - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Set album - $query = Database::prepare($this->database, "UPDATE ? SET album = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->photoIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setTags($tags) { - - # Functions sets the tags of a photo - # Excepts the following: - # (string) $tags = Comma separated list of tags with a maximum length of 1000 chars - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Parse tags - $tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags); - $tags = preg_replace('/,$|^,|(\ ){0,}$/', '', $tags); - - # Set tags - $query = Database::prepare($this->database, "UPDATE ? SET tags = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $tags, $this->photoIDs)); - $result = $this->database->query($query); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function duplicate() { - - # Functions duplicates a photo - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get photos - $query = Database::prepare($this->database, "SELECT id, checksum FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - if (!$photos) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - # For each photo - while ($photo = $photos->fetch_object()) { - - # Generate id - $id = str_replace('.', '', microtime(true)); - while(strlen($id)<14) $id .= 0; - - # Duplicate entry - $values = array(LYCHEE_TABLE_PHOTOS, $id, LYCHEE_TABLE_PHOTOS, $photo->id); - $query = Database::prepare($this->database, "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum) SELECT '?' AS id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum FROM ? WHERE id = '?'", $values); - $duplicate = $this->database->query($query); - if (!$duplicate) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - } - - return true; - - } - - public function delete() { - - # Functions deletes a photo with all its data and files - # Returns the following: - # (boolean) true = Success - # (boolean) false = Failure - - # Check dependencies - self::dependencies(isset($this->database, $this->photoIDs)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Get photos - $query = Database::prepare($this->database, "SELECT id, url, thumbUrl, checksum FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->photoIDs)); - $photos = $this->database->query($query); - if (!$photos) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - # For each photo - while ($photo = $photos->fetch_object()) { - - # Check if other photos are referring to this images - # If so, only delete the db entry - if ($this->exists($photo->checksum, $photo->id)===false) { - - # Get retina thumb url - $thumbUrl2x = explode(".", $photo->thumbUrl); - $thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1]; - - # Delete big - if (file_exists(LYCHEE_UPLOADS_BIG . $photo->url)&&!unlink(LYCHEE_UPLOADS_BIG . $photo->url)) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/big/'); - return false; - } - - # Delete medium - if (file_exists(LYCHEE_UPLOADS_MEDIUM . $photo->url)&&!unlink(LYCHEE_UPLOADS_MEDIUM . $photo->url)) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/medium/'); - return false; - } - - # Delete thumb - if (file_exists(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)&&!unlink(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/thumb/'); - return false; - } - - # Delete thumb@2x - if (file_exists(LYCHEE_UPLOADS_THUMB . $thumbUrl2x)&&!unlink(LYCHEE_UPLOADS_THUMB . $thumbUrl2x)) { - Log::error($this->database, __METHOD__, __LINE__, 'Could not delete high-res photo in uploads/thumb/'); - return false; - } - - } - - # Delete db entry - $query = Database::prepare($this->database, "DELETE FROM ? WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $photo->id)); - $delete = $this->database->query($query); - if (!$delete) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return true; - - } - -} - -?> diff --git a/php/modules/Plugins.php b/php/modules/Plugins.php deleted file mode 100644 index 9663814..0000000 --- a/php/modules/Plugins.php +++ /dev/null @@ -1,94 +0,0 @@ -files = $files; - - # Load plugins - foreach ($this->files as $file) { - - if ($file==='') continue; - - $file = LYCHEE_PLUGINS . $file; - - if (file_exists($file)===false) { - Log::warning($database, __METHOD__, __LINE__, 'Could not include plugin. File does not exist (' . $file . ').'); - continue; - } - - include($file); - - } - - return true; - - } - - public function attach(\SplObserver $observer) { - - if (!isset($observer)) return false; - - # Add observer - $this->observers[] = $observer; - - return true; - - } - - public function detach(\SplObserver $observer) { - - if (!isset($observer)) return false; - - # Remove observer - $key = array_search($observer, $this->observers, true); - if ($key) unset($this->observers[$key]); - - return true; - - } - - public function notify() { - - # Notify each observer - foreach ($this->observers as $value) $value->update($this); - - return true; - - } - - public function activate($action, $args) { - - if (!isset($action, $args)) return false; - - # Save vars - $this->action = $action; - $this->args = $args; - - # Notify observers - $this->notify(); - - return true; - - } - -} - -?> diff --git a/php/modules/Session.php b/php/modules/Session.php deleted file mode 100755 index 16916c9..0000000 --- a/php/modules/Session.php +++ /dev/null @@ -1,156 +0,0 @@ -plugins = $plugins; - $this->settings = $settings; - - return true; - - } - - public function init($database, $dbName, $public, $version) { - - # Check dependencies - self::dependencies(isset($this->settings, $public, $version)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - # Update - if (!isset($this->settings['version'])||$this->settings['version']!==$version) { - if (!Database::update($database, $dbName, @$this->settings['version'])) { - Log::error($database, __METHOD__, __LINE__, 'Updating the database failed'); - exit('Error: Updating the database failed!'); - } - } - - # Return settings - $return['config'] = $this->settings; - - # Remove username and password from response - unset($return['config']['username']); - unset($return['config']['password']); - - # Remove identifier from response - unset($return['config']['identifier']); - - # Path to Lychee for the server-import dialog - $return['config']['location'] = LYCHEE; - - # Check if login credentials exist and login if they don't - if ($this->noLogin()===true) { - $public = false; - $return['config']['login'] = false; - } else { - $return['config']['login'] = true; - } - - if ($public===false) { - - # Logged in - $return['status'] = LYCHEE_STATUS_LOGGEDIN; - - } else { - - # Logged out - $return['status'] = LYCHEE_STATUS_LOGGEDOUT; - - # Unset unused vars - unset($return['config']['thumbQuality']); - unset($return['config']['sortingAlbums']); - unset($return['config']['sortingPhotos']); - unset($return['config']['dropboxKey']); - unset($return['config']['login']); - unset($return['config']['location']); - unset($return['config']['imagick']); - unset($return['config']['medium']); - unset($return['config']['plugins']); - - } - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return $return; - - } - - public function login($username, $password) { - - # Check dependencies - self::dependencies(isset($this->settings, $username, $password)); - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - $username = crypt($username, $this->settings['username']); - $password = crypt($password, $this->settings['password']); - - # Check login with crypted hash - if ($this->settings['username']===$username&& - $this->settings['password']===$password) { - $_SESSION['login'] = true; - $_SESSION['identifier'] = $this->settings['identifier']; - return true; - } - - # No login - if ($this->noLogin()===true) return true; - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return false; - - } - - private function noLogin() { - - # Check dependencies - self::dependencies(isset($this->settings)); - - # Check if login credentials exist and login if they don't - if ($this->settings['username']===''&& - $this->settings['password']==='') { - $_SESSION['login'] = true; - $_SESSION['identifier'] = $this->settings['identifier']; - return true; - } - - return false; - - } - - public function logout() { - - # Call plugins - $this->plugins(__METHOD__, 0, func_get_args()); - - $_SESSION['login'] = null; - $_SESSION['identifier'] = null; - - session_destroy(); - - # Call plugins - $this->plugins(__METHOD__, 1, func_get_args()); - - return true; - - } - -} - -?> \ No newline at end of file diff --git a/php/modules/Settings.php b/php/modules/Settings.php deleted file mode 100755 index f0f57f4..0000000 --- a/php/modules/Settings.php +++ /dev/null @@ -1,255 +0,0 @@ -database = $database; - - return true; - - } - - public function get() { - - # Check dependencies - self::dependencies(isset($this->database)); - - # Execute query - $query = Database::prepare($this->database, "SELECT * FROM ?", array(LYCHEE_TABLE_SETTINGS)); - $settings = $this->database->query($query); - - # Add each to return - while ($setting = $settings->fetch_object()) $return[$setting->key] = $setting->value; - - # Fallback for versions below v2.5 - if (!isset($return['plugins'])) $return['plugins'] = ''; - - return $return; - - } - - public function setLogin($oldPassword = '', $username, $password) { - - # Check dependencies - self::dependencies(isset($this->database)); - - # Load settings - $settings = $this->get(); - - if ($oldPassword===$settings['password']||$settings['password']===crypt($oldPassword, $settings['password'])) { - - # Save username - if ($this->setUsername($username)!==true) exit('Error: Updating username failed!'); - - # Save password - if ($this->setPassword($password)!==true) exit('Error: Updating password failed!'); - - return true; - - } - - exit('Error: Current password entered incorrectly!'); - - } - - private function setUsername($username) { - - # Check dependencies - self::dependencies(isset($this->database)); - - # Hash username - $username = getHashedString($username); - - # Execute query - # Do not prepare $username because it is hashed and save - # Preparing (escaping) the username would destroy the hash - $query = Database::prepare($this->database, "UPDATE ? SET value = '$username' WHERE `key` = 'username'", array(LYCHEE_TABLE_SETTINGS)); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - private function setPassword($password) { - - # Check dependencies - self::dependencies(isset($this->database)); - - # Hash password - $password = getHashedString($password); - - # Execute query - # Do not prepare $password because it is hashed and save - # Preparing (escaping) the password would destroy the hash - $query = Database::prepare($this->database, "UPDATE ? SET value = '$password' WHERE `key` = 'password'", array(LYCHEE_TABLE_SETTINGS)); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setDropboxKey($key) { - - # Check dependencies - self::dependencies(isset($this->database, $key)); - - if (strlen($key)<1||strlen($key)>50) { - Log::notice($this->database, __METHOD__, __LINE__, 'Dropbox key is either too short or too long'); - return false; - } - - # Execute query - $query = Database::prepare($this->database, "UPDATE ? SET value = '?' WHERE `key` = 'dropboxKey'", array(LYCHEE_TABLE_SETTINGS, $key)); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setSortingPhotos($type, $order) { - - # Check dependencies - self::dependencies(isset($this->database, $type, $order)); - - $sorting = 'ORDER BY '; - - # Set row - switch ($type) { - - case 'id': $sorting .= 'id'; - break; - - case 'title': $sorting .= 'title'; - break; - - case 'description': $sorting .= 'description'; - break; - - case 'public': $sorting .= 'public'; - break; - - case 'type': $sorting .= 'type'; - break; - - case 'star': $sorting .= 'star'; - break; - - case 'takestamp': $sorting .= 'takestamp'; - break; - - default: exit('Error: Unknown type for sorting!'); - - } - - $sorting .= ' '; - - # Set order - switch ($order) { - - case 'ASC': $sorting .= 'ASC'; - break; - - case 'DESC': $sorting .= 'DESC'; - break; - - default: exit('Error: Unknown order for sorting!'); - - } - - # Execute query - # Do not prepare $sorting because it is a true statement - # Preparing (escaping) the sorting would destroy it - # $sorting is save and can't contain user-input - $query = Database::prepare($this->database, "UPDATE ? SET value = '$sorting' WHERE `key` = 'sortingPhotos'", array(LYCHEE_TABLE_SETTINGS)); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - - public function setSortingAlbums($type, $order) { - - # Check dependencies - self::dependencies(isset($this->database, $type, $order)); - - $sorting = 'ORDER BY '; - - # Set row - switch ($type) { - - case 'id': $sorting .= 'id'; - break; - - case 'title': $sorting .= 'title'; - break; - - case 'description': $sorting .= 'description'; - break; - - case 'public': $sorting .= 'public'; - break; - - default: exit('Error: Unknown type for sorting!'); - - } - - $sorting .= ' '; - - # Set order - switch ($order) { - - case 'ASC': $sorting .= 'ASC'; - break; - - case 'DESC': $sorting .= 'DESC'; - break; - - default: exit('Error: Unknown order for sorting!'); - - } - - # Execute query - # Do not prepare $sorting because it is a true statement - # Preparing (escaping) the sorting would destroy it - # $sorting is save and can't contain user-input - $query = Database::prepare($this->database, "UPDATE ? SET value = '$sorting' WHERE `key` = 'sortingAlbums'", array(LYCHEE_TABLE_SETTINGS)); - $result = $this->database->query($query); - - if (!$result) { - Log::error($this->database, __METHOD__, __LINE__, $this->database->error); - return false; - } - return true; - - } - -} - -?> \ No newline at end of file diff --git a/php/modules/misc.php b/php/modules/misc.php deleted file mode 100755 index 865d812..0000000 --- a/php/modules/misc.php +++ /dev/null @@ -1,199 +0,0 @@ - null, - 'albums' => null, - 'hash' => '' - ); - - ### - # Photos - ### - - $query = Database::prepare($database, "SELECT id, title, tags, public, star, album, thumbUrl, takestamp, url FROM ? WHERE title LIKE '%?%' OR description LIKE '%?%' OR tags LIKE '%?%'", array(LYCHEE_TABLE_PHOTOS, $term, $term, $term)); - $result = $database->query($query); - - while($photo = $result->fetch_assoc()) { - - $photo = Photo::prepareData($photo); - $return['photos'][$photo['id']] = $photo; - - } - - ### - # Albums - ### - - $query = Database::prepare($database, "SELECT id, title, public, sysstamp, password FROM ? WHERE title LIKE '%?%' OR description LIKE '%?%'", array(LYCHEE_TABLE_ALBUMS, $term, $term)); - $result = $database->query($query); - - while($album = $result->fetch_assoc()) { - - # Turn data from the database into a front-end friendly format - $album = Album::prepareData($album); - - # Thumbs - $query = Database::prepare($database, "SELECT thumbUrl FROM ? WHERE album = '?' " . $settings['sortingPhotos'] . " LIMIT 0, 3", array(LYCHEE_TABLE_PHOTOS, $album['id'])); - $thumbs = $database->query($query); - - # For each thumb - $k = 0; - while ($thumb = $thumbs->fetch_object()) { - $album['thumbs'][$k] = LYCHEE_URL_UPLOADS_THUMB . $thumb->thumbUrl; - $k++; - } - - # Add to return - $return['albums'][$album['id']] = $album; - - } - - # Hash - $return['hash'] = md5(json_encode($return)); - - return $return; - -} - -function getGraphHeader($database, $photoID) { - - if (!isset($database, $photoID)) return false; - - $photo = new Photo($database, null, null, $photoID); - if ($photo->getPublic('')===false) return false; - - $query = Database::prepare($database, "SELECT title, description, url, medium FROM ? WHERE id = '?'", array(LYCHEE_TABLE_PHOTOS, $photoID)); - $result = $database->query($query); - $row = $result->fetch_object(); - - if (!$result||!$row) return false; - - if ($row->medium==='1') $dir = 'medium'; - else $dir = 'big'; - - $parseUrl = parse_url('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); - $url = $parseUrl['scheme'] . '://' . $parseUrl['host'] . $parseUrl['path'] . '?' . $parseUrl['query']; - $picture = $parseUrl['scheme'] . '://' . $parseUrl['host'] . $parseUrl['path'] . '/../uploads/' . $dir . '/' . $row->url; - - $url = htmlentities($url); - $picture = htmlentities($picture); - - $row->title = htmlentities($row->title); - $row->description = htmlentities($row->description); - - $return = ''; - $return .= ''; - $return .= ''; - $return .= ''; - - $return .= ''; - $return .= ''; - $return .= ''; - $return .= ''; - - $return .= ''; - $return .= ''; - $return .= ''; - $return .= ''; - $return .= ''; - - return $return; - -} - -function getExtension($filename) { - - $extension = strpos($filename, '.') !== false - ? strrchr($filename, '.') - : ''; - - return $extension; - -} - -function getHashedString($password) { - - # Inspired by http://alias.io/2010/01/store-passwords-safely-with-php-and-mysql/ - - # A higher $cost is more secure but consumes more processing power - $cost = 10; - - # Create a random salt - if (extension_loaded('openssl')) { - $salt = strtr(substr(base64_encode(openssl_random_pseudo_bytes(17)),0,22), '+', '.'); - } elseif (extension_loaded('mcrypt')) { - $salt = strtr(substr(base64_encode(mcrypt_create_iv(17, MCRYPT_DEV_URANDOM)),0,22), '+', '.'); - } else { - $salt = ""; - for ($i = 0; $i < 22; $i++) { - $salt .= substr("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", mt_rand(0, 63), 1); - } - } - - # Prefix information about the hash so PHP knows how to verify it later. - # "$2a$" Means we're using the Blowfish algorithm. The following two digits are the cost parameter. - $salt = sprintf("$2a$%02d$", $cost) . $salt; - - # Hash the password with the salt - return crypt($password, $salt); - -} - -function hasPermissions($path) { - - // Check if the given path is readable and writable - // Both functions are also verifying that the path exists - if (is_readable($path)===true&& - is_writeable($path)===true) return true; - - return false; - -} - -function fastimagecopyresampled(&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 4) { - - ### - # Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled. - # Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled". - # Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting. - # Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain. - # - # Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero. - # Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect. - # 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized. - # 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3. - # 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster. - # 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images. - # 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled. - ### - - if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; } - - if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) { - - $temp = imagecreatetruecolor($dst_w * $quality + 1, $dst_h * $quality + 1); - imagecopyresized($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h); - imagecopyresampled($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality); - imagedestroy($temp); - - } else imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); - - return true; - -} - -?> diff --git a/plugins/Diagnostics/index.php b/plugins/Diagnostics/index.php new file mode 100644 index 0000000..c7cf963 --- /dev/null +++ b/plugins/Diagnostics/index.php @@ -0,0 +1,141 @@ +server_version<50500) echo('Warning: Lychee uses the GBK charset to avoid sql injections on your MySQL version. Please update to MySQL 5.5 or higher to enable UTF-8 support.' . PHP_EOL); + +// Output +if ($error==='') echo('No critical problems found. Lychee should work without problems!' . PHP_EOL); +else echo $error; + +// Show separator +echo(PHP_EOL . PHP_EOL . 'System Information' . PHP_EOL); +echo('------------------' . PHP_EOL); + +// Ensure that user is logged in +if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&& + (isset($_SESSION['identifier'])&&$_SESSION['identifier']===$settings['identifier'])) { + + // Load json + $json = file_get_contents(LYCHEE_SRC . 'package.json'); + $json = json_decode($json, true); + + // About imagick + $imagick = extension_loaded('imagick'); + if ($imagick===true) $imagickVersion = @Imagick::getVersion(); + else $imagick = '-'; + if (!isset($imagickVersion, $imagickVersion['versionNumber'])||$imagickVersion==='') $imagickVersion = '-'; + else $imagickVersion = $imagickVersion['versionNumber']; + + // Output system information + echo('Lychee Version: ' . $json['version'] . PHP_EOL); + echo('DB Version: ' . $settings['version'] . PHP_EOL); + echo('System: ' . PHP_OS . PHP_EOL); + echo('PHP Version: ' . floatval(phpversion()) . PHP_EOL); + echo('MySQL Version: ' . $database->server_version . PHP_EOL); + echo('Imagick: ' . $imagick . PHP_EOL); + echo('Imagick Active: ' . $settings['imagick'] . PHP_EOL); + echo('Imagick Version: ' . $imagickVersion . PHP_EOL); + echo('GD Version: ' . $gdVersion['GD Version'] . PHP_EOL); + echo('Plugins: ' . implode($settings['plugins'], ', ') . PHP_EOL); + +} else { + + // Don't go further if the user is not logged in + exit('You have to be logged in to see more information.'); + +} + +?> \ No newline at end of file diff --git a/plugins/Log/index.php b/plugins/Log/index.php new file mode 100644 index 0000000..4964eb5 --- /dev/null +++ b/plugins/Log/index.php @@ -0,0 +1,64 @@ +query($query); + + // Output + if ($result->num_rows===0) { + + echo('Everything looks fine, Lychee has not reported any problems!'); + + } else { + + while($row = $result->fetch_row()) { + + // Encode result before printing + $row = array_map('htmlentities', $row); + + // Format: time TZ - type - function(line) - text + printf("%s - %s - %s (%s) \t- %s\n", $row[0], $row[1], $row[2], $row[3], $row[4]); + + } + + } + +} else { + + // Don't go further if the user is not logged in + exit('You have to be logged in to see the log.'); + +} + +?> \ No newline at end of file diff --git a/plugins/check/index.php b/plugins/check/index.php deleted file mode 100644 index b44519e..0000000 --- a/plugins/check/index.php +++ /dev/null @@ -1,143 +0,0 @@ -get(); - -# Config -if (!isset($dbName)||$dbName==='') $error .= ('Error: No property for $dbName in config.php' . PHP_EOL); -if (!isset($dbUser)||$dbUser==='') $error .= ('Error: No property for $dbUser in config.php' . PHP_EOL); -if (!isset($dbPassword)) $error .= ('Error: No property for $dbPassword in config.php' . PHP_EOL); -if (!isset($dbHost)||$dbHost==='') $error .= ('Error: No property for $dbHost in config.php' . PHP_EOL); - -# Settings -if (!isset($settings['username'])||$settings['username']=='') $error .= ('Error: Username empty or not set in database' . PHP_EOL); -if (!isset($settings['password'])||$settings['password']=='') $error .= ('Error: Password empty or not set in database' . PHP_EOL); -if (!isset($settings['thumbQuality'])||$settings['thumbQuality']=='') $error .= ('Error: No or wrong property for thumbQuality in database' . PHP_EOL); -if (!isset($settings['sortingPhotos'])||$settings['sortingPhotos']=='') $error .= ('Error: Wrong property for sortingPhotos in database' . PHP_EOL); -if (!isset($settings['sortingAlbums'])||$settings['sortingAlbums']=='') $error .= ('Error: Wrong property for sortingAlbums in database' . PHP_EOL); -if (!isset($settings['plugins'])) $error .= ('Error: No property for plugins in database' . PHP_EOL); -if (!isset($settings['imagick'])||$settings['imagick']=='') $error .= ('Error: No or wrong property for imagick in database' . PHP_EOL); -if (!isset($settings['identifier'])||$settings['identifier']=='') $error .= ('Error: No or wrong property for identifier in database' . PHP_EOL); -if (!isset($settings['skipDuplicates'])||$settings['skipDuplicates']=='') $error .= ('Error: No or wrong property for skipDuplicates in database' . PHP_EOL); -if (!isset($settings['checkForUpdates'])||($settings['checkForUpdates']!='0'&&$settings['checkForUpdates']!='1')) $error .= ('Error: No or wrong property for checkForUpdates in database' . PHP_EOL); - -# Check dropboxKey -if (!$settings['dropboxKey']) echo('Warning: Dropbox import not working. No property for dropboxKey' . PHP_EOL); - -# Check php.ini Settings -if (ini_get('max_execution_time')<200&&ini_set('upload_max_filesize', '20M')===false) echo('Warning: You may experience problems when uploading a large amount of photos. Take a look in the FAQ for details.' . PHP_EOL); -if (!ini_get('allow_url_fopen')) echo('Warning: You may experience problems with the Dropbox- and URL-Import. Edit your php.ini and set allow_url_fopen to 1.' . PHP_EOL); - -# Check mysql version -if ($database->server_version<50500) echo('Warning: Lychee uses the GBK charset to avoid sql injections on your MySQL version. Please update to MySQL 5.5 or higher to enable UTF-8 support.' . PHP_EOL); - -# About GD -$gdVersion = gd_info(); -if (!$gdVersion['JPEG Support']) $error .= ('Error: PHP gd extension without jpeg support' . PHP_EOL); -if (!$gdVersion['PNG Support']) $error .= ('Error: PHP gd extension without png support' . PHP_EOL); -if (!$gdVersion['GIF Read Support'] || !$gdVersion['GIF Create Support']) $error .= ('Error: PHP gd extension without full gif support' . PHP_EOL); - -# Output -if ($error==='') echo('No critical problems found. Lychee should work without problems!' . PHP_EOL); -else echo $error; - -# Show separator -echo(PHP_EOL . PHP_EOL . 'System Information' . PHP_EOL); -echo('------------------' . PHP_EOL); - -# Ensure that user is logged in -session_start(); - -if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&& - (isset($_SESSION['identifier'])&&$_SESSION['identifier']===$settings['identifier'])) { - - # Load json - $json = file_get_contents(LYCHEE_SRC . 'package.json'); - $json = json_decode($json, true); - - # About imagick - $imagick = extension_loaded('imagick'); - if ($imagick===true) $imagickVersion = @Imagick::getVersion(); - else $imagick = '-'; - if (!isset($imagickVersion, $imagickVersion['versionNumber'])||$imagickVersion==='') $imagickVersion = '-'; - else $imagickVersion = $imagickVersion['versionNumber']; - - # Output system information - echo('Lychee Version: ' . $json['version'] . PHP_EOL); - echo('DB Version: ' . $settings['version'] . PHP_EOL); - echo('System: ' . PHP_OS . PHP_EOL); - echo('PHP Version: ' . floatval(phpversion()) . PHP_EOL); - echo('MySQL Version: ' . $database->server_version . PHP_EOL); - echo('Imagick: ' . $imagick . PHP_EOL); - echo('Imagick Active: ' . $settings['imagick'] . PHP_EOL); - echo('Imagick Version: ' . $imagickVersion . PHP_EOL); - echo('GD Version: ' . $gdVersion['GD Version'] . PHP_EOL); - echo('Plugins: ' . $settings['plugins'] . PHP_EOL); - -} else { - - # Don't go further if the user is not logged in - echo('You have to be logged in to see more information.'); - exit(); - -} - -?> diff --git a/plugins/displaylog/index.php b/plugins/displaylog/index.php deleted file mode 100644 index e6e690b..0000000 --- a/plugins/displaylog/index.php +++ /dev/null @@ -1,81 +0,0 @@ -get(); - -# Ensure that user is logged in -session_start(); - -if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&& - (isset($_SESSION['identifier'])&&$_SESSION['identifier']===$settings['identifier'])) { - - # Result - $query = Database::prepare($database, "SELECT FROM_UNIXTIME(time), type, function, line, text FROM ?", array(LYCHEE_TABLE_LOG)); - $result = $database->query($query); - - # Output - if ($result->num_rows===0) { - - echo('Everything looks fine, Lychee has not reported any problems!'); - - } else { - - while($row = $result->fetch_row()) { - - # Encode result before printing - $row = array_map('htmlentities', $row); - - # Format: time TZ - type - function(line) - text - printf ("%s - %s - %s (%s) \t- %s\n", $row[0], $row[1], $row[2], $row[3], $row[4]); - - } - - } - -} else { - - # Don't go further if the user is not logged in - echo('You have to be logged in to see the log.'); - exit(); - -} - -?> diff --git a/src/npm-shrinkwrap.json b/src/npm-shrinkwrap.json new file mode 100644 index 0000000..9622ee0 --- /dev/null +++ b/src/npm-shrinkwrap.json @@ -0,0 +1,2141 @@ +{ + "name": "Lychee", + "version": "3.1.0", + "dependencies": { + "abbrev": { + "version": "1.0.7", + "from": "abbrev@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz" + }, + "align-text": { + "version": "0.1.4", + "from": "align-text@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz" + }, + "amdefine": { + "version": "1.0.0", + "from": "amdefine@>=0.0.4", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" + }, + "ansi": { + "version": "0.3.1", + "from": "ansi@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" + }, + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + }, + "ansi-styles": { + "version": "2.2.0", + "from": "ansi-styles@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.0.tgz" + }, + "archy": { + "version": "1.0.0", + "from": "archy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" + }, + "are-we-there-yet": { + "version": "1.1.2", + "from": "are-we-there-yet@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" + }, + "array-differ": { + "version": "1.0.0", + "from": "array-differ@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz" + }, + "array-find-index": { + "version": "1.0.1", + "from": "array-find-index@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz" + }, + "array-index": { + "version": "1.0.0", + "from": "array-index@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/array-index/-/array-index-1.0.0.tgz" + }, + "array-union": { + "version": "1.0.1", + "from": "array-union@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz" + }, + "array-uniq": { + "version": "1.0.2", + "from": "array-uniq@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz" + }, + "asn1": { + "version": "0.2.3", + "from": "asn1@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, + "async": { + "version": "1.5.2", + "from": "async@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + }, + "async-foreach": { + "version": "0.1.3", + "from": "async-foreach@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz" + }, + "autoprefixer": { + "version": "6.3.3", + "from": "autoprefixer@>=6.0.0 <7.0.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.3.3.tgz" + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" + }, + "aws4": { + "version": "1.3.2", + "from": "aws4@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.3.2.tgz", + "dependencies": { + "lru-cache": { + "version": "4.0.0", + "from": "lru-cache@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.0.tgz" + } + } + }, + "babel-code-frame": { + "version": "6.7.2", + "from": "babel-code-frame@>=6.7.2 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.7.2.tgz" + }, + "babel-core": { + "version": "6.7.2", + "from": "babel-core@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.7.2.tgz" + }, + "babel-generator": { + "version": "6.7.2", + "from": "babel-generator@>=6.7.2 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.7.2.tgz" + }, + "babel-helper-call-delegate": { + "version": "6.6.5", + "from": "babel-helper-call-delegate@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.6.5.tgz" + }, + "babel-helper-define-map": { + "version": "6.6.5", + "from": "babel-helper-define-map@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.6.5.tgz" + }, + "babel-helper-function-name": { + "version": "6.6.0", + "from": "babel-helper-function-name@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.6.0.tgz" + }, + "babel-helper-get-function-arity": { + "version": "6.6.5", + "from": "babel-helper-get-function-arity@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.6.5.tgz" + }, + "babel-helper-hoist-variables": { + "version": "6.6.5", + "from": "babel-helper-hoist-variables@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.6.5.tgz" + }, + "babel-helper-optimise-call-expression": { + "version": "6.6.0", + "from": "babel-helper-optimise-call-expression@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.6.0.tgz" + }, + "babel-helper-regex": { + "version": "6.6.5", + "from": "babel-helper-regex@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.6.5.tgz" + }, + "babel-helper-replace-supers": { + "version": "6.7.0", + "from": "babel-helper-replace-supers@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.7.0.tgz" + }, + "babel-helpers": { + "version": "6.6.0", + "from": "babel-helpers@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.6.0.tgz" + }, + "babel-messages": { + "version": "6.7.2", + "from": "babel-messages@>=6.7.2 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.7.2.tgz" + }, + "babel-plugin-check-es2015-constants": { + "version": "6.7.2", + "from": "babel-plugin-check-es2015-constants@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.7.2.tgz" + }, + "babel-plugin-syntax-async-functions": { + "version": "6.5.0", + "from": "babel-plugin-syntax-async-functions@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.5.0.tgz" + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.5.2", + "from": "babel-plugin-transform-es2015-arrow-functions@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.5.2.tgz" + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-block-scoped-functions@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.7.1", + "from": "babel-plugin-transform-es2015-block-scoping@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.7.1.tgz" + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-classes@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-computed-properties@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-destructuring@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.6.4", + "from": "babel-plugin-transform-es2015-duplicate-keys@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.6.4.tgz" + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.6.0", + "from": "babel-plugin-transform-es2015-for-of@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.6.0.tgz" + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.5.0", + "from": "babel-plugin-transform-es2015-function-name@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.5.0.tgz" + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.5.0", + "from": "babel-plugin-transform-es2015-literals@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.5.0.tgz" + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.7.0", + "from": "babel-plugin-transform-es2015-modules-commonjs@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.7.0.tgz" + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-object-super@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.7.0", + "from": "babel-plugin-transform-es2015-parameters@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.7.0.tgz" + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.5.0", + "from": "babel-plugin-transform-es2015-shorthand-properties@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.5.0.tgz" + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-spread@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.5.0", + "from": "babel-plugin-transform-es2015-sticky-regex@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.5.0.tgz" + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.6.5", + "from": "babel-plugin-transform-es2015-template-literals@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.6.5.tgz" + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.6.0", + "from": "babel-plugin-transform-es2015-typeof-symbol@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.6.0.tgz" + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.5.0", + "from": "babel-plugin-transform-es2015-unicode-regex@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.5.0.tgz" + }, + "babel-plugin-transform-regenerator": { + "version": "6.6.5", + "from": "babel-plugin-transform-regenerator@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.6.5.tgz" + }, + "babel-plugin-transform-strict-mode": { + "version": "6.6.5", + "from": "babel-plugin-transform-strict-mode@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.6.5.tgz" + }, + "babel-preset-es2015": { + "version": "6.6.0", + "from": "babel-preset-es2015@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.6.0.tgz" + }, + "babel-register": { + "version": "6.7.2", + "from": "babel-register@>=6.7.2 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.7.2.tgz", + "dependencies": { + "core-js": { + "version": "2.2.0", + "from": "core-js@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.2.0.tgz" + } + } + }, + "babel-runtime": { + "version": "5.8.35", + "from": "babel-runtime@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz" + }, + "babel-template": { + "version": "6.7.0", + "from": "babel-template@>=6.7.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.7.0.tgz" + }, + "babel-traverse": { + "version": "6.7.3", + "from": "babel-traverse@>=6.7.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.7.3.tgz" + }, + "babel-types": { + "version": "6.7.2", + "from": "babel-types@>=6.7.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.7.2.tgz" + }, + "babylon": { + "version": "6.7.0", + "from": "babylon@>=6.7.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.7.0.tgz" + }, + "balanced-match": { + "version": "0.3.0", + "from": "balanced-match@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz" + }, + "basiccontext": { + "version": "3.5.1", + "from": "basiccontext@>=3.5.1 <4.0.0" + }, + "basicmodal": { + "version": "3.3.3", + "from": "basicmodal@>=3.3.3 <4.0.0" + }, + "beeper": { + "version": "1.1.0", + "from": "beeper@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz" + }, + "bl": { + "version": "1.0.3", + "from": "bl@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.5 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + } + }, + "block-stream": { + "version": "0.0.8", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" + }, + "boom": { + "version": "2.10.1", + "from": "boom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + }, + "brace-expansion": { + "version": "1.1.3", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz" + }, + "browserslist": { + "version": "1.1.3", + "from": "browserslist@>=1.1.3 <1.2.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.1.3.tgz" + }, + "bufferstreams": { + "version": "1.0.1", + "from": "bufferstreams@1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz" + }, + "builtin-modules": { + "version": "1.1.1", + "from": "builtin-modules@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" + }, + "camelcase": { + "version": "2.1.1", + "from": "camelcase@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz" + }, + "camelcase-keys": { + "version": "2.1.0", + "from": "camelcase-keys@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz" + }, + "caniuse-db": { + "version": "1.0.30000430", + "from": "caniuse-db@>=1.0.30000409 <2.0.0", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000430.tgz" + }, + "caseless": { + "version": "0.11.0", + "from": "caseless@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" + }, + "center-align": { + "version": "0.1.3", + "from": "center-align@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz" + }, + "chalk": { + "version": "1.1.1", + "from": "chalk@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz" + }, + "clean-css": { + "version": "3.4.10", + "from": "clean-css@>=3.3.3 <4.0.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.10.tgz", + "dependencies": { + "source-map": { + "version": "0.4.4", + "from": "source-map@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" + } + } + }, + "cliui": { + "version": "3.1.0", + "from": "cliui@>=3.0.3 <4.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.1.0.tgz" + }, + "clone": { + "version": "1.0.2", + "from": "clone@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz" + }, + "clone-stats": { + "version": "0.0.1", + "from": "clone-stats@>=0.0.1 <0.0.2", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz" + }, + "code-point-at": { + "version": "1.0.0", + "from": "code-point-at@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz" + }, + "color-convert": { + "version": "1.0.0", + "from": "color-convert@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.0.0.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, + "commander": { + "version": "2.8.1", + "from": "commander@>=2.8.0 <2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "concat-stream": { + "version": "1.5.1", + "from": "concat-stream@>=1.4.7 <2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz", + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + } + }, + "concat-with-sourcemaps": { + "version": "1.0.4", + "from": "concat-with-sourcemaps@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.4.tgz" + }, + "config-chain": { + "version": "1.1.10", + "from": "config-chain@>=1.1.8 <1.2.0", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.10.tgz" + }, + "convert-source-map": { + "version": "1.2.0", + "from": "convert-source-map@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.2.0.tgz" + }, + "core-js": { + "version": "1.2.6", + "from": "core-js@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "cross-spawn": { + "version": "2.1.5", + "from": "cross-spawn@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-2.1.5.tgz" + }, + "cross-spawn-async": { + "version": "2.1.9", + "from": "cross-spawn-async@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.1.9.tgz", + "dependencies": { + "lru-cache": { + "version": "4.0.0", + "from": "lru-cache@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.0.tgz" + } + } + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "d": { + "version": "0.1.1", + "from": "d@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" + }, + "dashdash": { + "version": "1.13.0", + "from": "dashdash@>=1.10.1 <2.0.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "dateformat": { + "version": "1.0.12", + "from": "dateformat@>=1.0.11 <2.0.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz" + }, + "deap": { + "version": "1.0.0", + "from": "deap@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/deap/-/deap-1.0.0.tgz" + }, + "debug": { + "version": "2.2.0", + "from": "debug@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" + }, + "decamelize": { + "version": "1.2.0", + "from": "decamelize@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + }, + "defaults": { + "version": "1.0.3", + "from": "defaults@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "delegates": { + "version": "1.0.0", + "from": "delegates@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + }, + "deprecated": { + "version": "0.0.1", + "from": "deprecated@>=0.0.1 <0.0.2", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz" + }, + "detect-indent": { + "version": "3.0.1", + "from": "detect-indent@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz" + }, + "duplexer": { + "version": "0.1.1", + "from": "duplexer@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz" + }, + "duplexer2": { + "version": "0.0.2", + "from": "duplexer2@0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz" + }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@>=0.0.1 <1.0.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" + }, + "end-of-stream": { + "version": "0.1.5", + "from": "end-of-stream@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz" + }, + "error-ex": { + "version": "1.3.0", + "from": "error-ex@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz" + }, + "es5-ext": { + "version": "0.10.11", + "from": "es5-ext@>=0.10.10 <0.11.0", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz" + }, + "es6-iterator": { + "version": "2.0.0", + "from": "es6-iterator@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" + }, + "es6-symbol": { + "version": "3.0.2", + "from": "es6-symbol@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz" + }, + "escape-string-regexp": { + "version": "1.0.5", + "from": "escape-string-regexp@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + }, + "esutils": { + "version": "2.0.2", + "from": "esutils@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" + }, + "event-stream": { + "version": "3.3.2", + "from": "event-stream@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.2.tgz" + }, + "extend": { + "version": "2.0.1", + "from": "extend@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-2.0.1.tgz" + }, + "extsprintf": { + "version": "1.0.2", + "from": "extsprintf@1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" + }, + "fancy-log": { + "version": "1.2.0", + "from": "fancy-log@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.2.0.tgz" + }, + "find-index": { + "version": "0.1.1", + "from": "find-index@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz" + }, + "find-up": { + "version": "1.1.2", + "from": "find-up@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "dependencies": { + "path-exists": { + "version": "2.1.0", + "from": "path-exists@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz" + } + } + }, + "findup-sync": { + "version": "0.3.0", + "from": "findup-sync@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz" + }, + "first-chunk-stream": { + "version": "1.0.0", + "from": "first-chunk-stream@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz" + }, + "flagged-respawn": { + "version": "0.3.1", + "from": "flagged-respawn@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.1.tgz" + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "1.0.0-rc4", + "from": "form-data@>=1.0.0-rc3 <1.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" + }, + "from": { + "version": "0.1.3", + "from": "from@>=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.3.tgz" + }, + "fstream": { + "version": "1.0.8", + "from": "fstream@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz" + }, + "gauge": { + "version": "1.2.7", + "from": "gauge@>=1.2.5 <1.3.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz" + }, + "gaze": { + "version": "0.5.2", + "from": "gaze@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz" + }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" + }, + "get-stdin": { + "version": "4.0.1", + "from": "get-stdin@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" + }, + "glob": { + "version": "5.0.15", + "from": "glob@>=5.0.0 <5.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + }, + "glob-stream": { + "version": "3.1.18", + "from": "glob-stream@>=3.1.5 <4.0.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "dependencies": { + "glob": { + "version": "4.5.3", + "from": "glob@>=4.3.1 <5.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz" + }, + "readable-stream": { + "version": "1.0.33", + "from": "readable-stream@>=1.0.33-1 <1.1.0-0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz" + }, + "through2": { + "version": "0.6.5", + "from": "through2@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" + } + } + }, + "glob-watcher": { + "version": "0.0.6", + "from": "glob-watcher@>=0.0.6 <0.0.7", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz" + }, + "glob2base": { + "version": "0.0.12", + "from": "glob2base@>=0.0.12 <0.0.13", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz" + }, + "globals": { + "version": "8.18.0", + "from": "globals@>=8.3.0 <9.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz" + }, + "globule": { + "version": "0.1.0", + "from": "globule@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "dependencies": { + "glob": { + "version": "3.1.21", + "from": "glob@>=3.1.21 <3.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz" + }, + "graceful-fs": { + "version": "1.2.3", + "from": "graceful-fs@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz" + }, + "inherits": { + "version": "1.0.2", + "from": "inherits@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz" + }, + "lodash": { + "version": "1.0.2", + "from": "lodash@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz" + }, + "minimatch": { + "version": "0.2.14", + "from": "minimatch@>=0.2.11 <0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + } + } + }, + "glogg": { + "version": "1.0.0", + "from": "glogg@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz" + }, + "graceful-fs": { + "version": "4.1.3", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" + }, + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>=1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + }, + "gulp": { + "version": "3.9.1", + "from": "gulp@>=3.9.1 <4.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz" + }, + "gulp-autoprefixer": { + "version": "3.1.0", + "from": "gulp-autoprefixer@3.1.0", + "resolved": "https://registry.npmjs.org/gulp-autoprefixer/-/gulp-autoprefixer-3.1.0.tgz" + }, + "gulp-babel": { + "version": "6.1.2", + "from": "gulp-babel@>=6.1.2 <7.0.0", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.2.tgz", + "dependencies": { + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + } + } + }, + "gulp-concat": { + "version": "2.6.0", + "from": "gulp-concat@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.0.tgz", + "dependencies": { + "readable-stream": { + "version": "1.0.33", + "from": "readable-stream@>=1.0.33-1 <1.1.0-0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz" + }, + "through2": { + "version": "0.6.5", + "from": "through2@>=0.6.3 <0.7.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" + } + } + }, + "gulp-inject": { + "version": "3.0.0", + "from": "gulp-inject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/gulp-inject/-/gulp-inject-3.0.0.tgz" + }, + "gulp-load-plugins": { + "version": "1.2.0", + "from": "gulp-load-plugins@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/gulp-load-plugins/-/gulp-load-plugins-1.2.0.tgz", + "dependencies": { + "findup-sync": { + "version": "0.2.1", + "from": "findup-sync@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.2.1.tgz" + }, + "glob": { + "version": "4.3.5", + "from": "glob@>=4.3.0 <4.4.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.3.5.tgz" + } + } + }, + "gulp-minify-css": { + "version": "1.2.4", + "from": "gulp-minify-css@>=1.2.4 <2.0.0", + "resolved": "https://registry.npmjs.org/gulp-minify-css/-/gulp-minify-css-1.2.4.tgz", + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + } + }, + "gulp-rimraf": { + "version": "0.2.0", + "from": "gulp-rimraf@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/gulp-rimraf/-/gulp-rimraf-0.2.0.tgz" + }, + "gulp-sass": { + "version": "2.2.0", + "from": "gulp-sass@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-2.2.0.tgz", + "dependencies": { + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + } + } + }, + "gulp-uglify": { + "version": "1.5.3", + "from": "gulp-uglify@>=1.5.3 <2.0.0", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-1.5.3.tgz" + }, + "gulp-util": { + "version": "3.0.7", + "from": "gulp-util@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.7.tgz" + }, + "gulplog": { + "version": "1.0.0", + "from": "gulplog@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz" + }, + "har-validator": { + "version": "2.0.6", + "from": "har-validator@>=2.0.6 <2.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "dependencies": { + "commander": { + "version": "2.9.0", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + } + } + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + }, + "has-flag": { + "version": "1.0.0", + "from": "has-flag@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" + }, + "has-gulplog": { + "version": "0.1.0", + "from": "has-gulplog@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz" + }, + "has-unicode": { + "version": "2.0.0", + "from": "has-unicode@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz" + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "home-or-tmp": { + "version": "1.0.0", + "from": "home-or-tmp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz" + }, + "hosted-git-info": { + "version": "2.1.4", + "from": "hosted-git-info@>=2.1.4 <3.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz" + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" + }, + "indent-string": { + "version": "2.1.0", + "from": "indent-string@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "dependencies": { + "repeating": { + "version": "2.0.0", + "from": "repeating@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz" + } + } + }, + "inflight": { + "version": "1.0.4", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "ini": { + "version": "1.3.4", + "from": "ini@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" + }, + "interpret": { + "version": "1.0.0", + "from": "interpret@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.0.tgz" + }, + "invariant": { + "version": "2.2.1", + "from": "invariant@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.1.tgz" + }, + "invert-kv": { + "version": "1.0.0", + "from": "invert-kv@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz" + }, + "is-absolute": { + "version": "0.1.7", + "from": "is-absolute@>=0.1.7 <0.2.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz" + }, + "is-arrayish": { + "version": "0.2.1", + "from": "is-arrayish@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + }, + "is-buffer": { + "version": "1.1.3", + "from": "is-buffer@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz" + }, + "is-builtin-module": { + "version": "1.0.0", + "from": "is-builtin-module@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz" + }, + "is-finite": { + "version": "1.0.1", + "from": "is-finite@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" + }, + "is-integer": { + "version": "1.0.6", + "from": "is-integer@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" + }, + "is-my-json-valid": { + "version": "2.13.1", + "from": "is-my-json-valid@>=2.12.4 <3.0.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" + }, + "is-property": { + "version": "1.0.2", + "from": "is-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + }, + "is-relative": { + "version": "0.1.3", + "from": "is-relative@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz" + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + }, + "is-utf8": { + "version": "0.2.1", + "from": "is-utf8@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "isexe": { + "version": "1.1.2", + "from": "isexe@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz" + }, + "isobject": { + "version": "2.0.0", + "from": "isobject@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.0.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "jodid25519": { + "version": "1.0.2", + "from": "jodid25519@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" + }, + "jquery": { + "version": "2.2.1", + "from": "jquery@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.1.tgz" + }, + "js-base64": { + "version": "2.1.9", + "from": "js-base64@>=2.1.9 <3.0.0", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz" + }, + "js-tokens": { + "version": "1.0.2", + "from": "js-tokens@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz" + }, + "jsbn": { + "version": "0.1.0", + "from": "jsbn@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" + }, + "jsesc": { + "version": "0.5.0", + "from": "jsesc@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + }, + "json-schema": { + "version": "0.2.2", + "from": "json-schema@0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "json5": { + "version": "0.4.0", + "from": "json5@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz" + }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, + "jsprim": { + "version": "1.2.2", + "from": "jsprim@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz" + }, + "kind-of": { + "version": "3.0.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.2.tgz" + }, + "lazy-cache": { + "version": "1.0.3", + "from": "lazy-cache@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.3.tgz" + }, + "lcid": { + "version": "1.0.0", + "from": "lcid@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" + }, + "left-pad": { + "version": "0.0.3", + "from": "left-pad@0.0.3", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz" + }, + "liftoff": { + "version": "2.2.0", + "from": "liftoff@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.2.0.tgz" + }, + "line-numbers": { + "version": "0.2.0", + "from": "line-numbers@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/line-numbers/-/line-numbers-0.2.0.tgz" + }, + "load-json-file": { + "version": "1.1.0", + "from": "load-json-file@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz" + }, + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.10.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + }, + "lodash._basecopy": { + "version": "3.0.1", + "from": "lodash._basecopy@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz" + }, + "lodash._basetostring": { + "version": "3.0.1", + "from": "lodash._basetostring@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" + }, + "lodash._basevalues": { + "version": "3.0.0", + "from": "lodash._basevalues@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz" + }, + "lodash._getnative": { + "version": "3.9.1", + "from": "lodash._getnative@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "from": "lodash._isiterateecall@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz" + }, + "lodash._reescape": { + "version": "3.0.0", + "from": "lodash._reescape@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz" + }, + "lodash._reevaluate": { + "version": "3.0.0", + "from": "lodash._reevaluate@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "from": "lodash._reinterpolate@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" + }, + "lodash._root": { + "version": "3.0.1", + "from": "lodash._root@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz" + }, + "lodash.escape": { + "version": "3.2.0", + "from": "lodash.escape@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz" + }, + "lodash.isarguments": { + "version": "3.0.8", + "from": "lodash.isarguments@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.8.tgz" + }, + "lodash.isarray": { + "version": "3.0.4", + "from": "lodash.isarray@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz" + }, + "lodash.keys": { + "version": "3.1.2", + "from": "lodash.keys@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + }, + "lodash.pad": { + "version": "4.1.0", + "from": "lodash.pad@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.1.0.tgz" + }, + "lodash.padend": { + "version": "4.2.0", + "from": "lodash.padend@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.2.0.tgz" + }, + "lodash.padstart": { + "version": "4.2.0", + "from": "lodash.padstart@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.2.0.tgz" + }, + "lodash.repeat": { + "version": "4.0.0", + "from": "lodash.repeat@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-4.0.0.tgz" + }, + "lodash.restparam": { + "version": "3.6.1", + "from": "lodash.restparam@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz" + }, + "lodash.template": { + "version": "3.6.2", + "from": "lodash.template@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz" + }, + "lodash.templatesettings": { + "version": "3.1.1", + "from": "lodash.templatesettings@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz" + }, + "lodash.tostring": { + "version": "4.1.2", + "from": "lodash.tostring@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.2.tgz" + }, + "longest": { + "version": "1.0.1", + "from": "longest@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" + }, + "loose-envify": { + "version": "1.1.0", + "from": "loose-envify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.1.0.tgz" + }, + "loud-rejection": { + "version": "1.3.0", + "from": "loud-rejection@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.3.0.tgz" + }, + "lru-cache": { + "version": "2.7.3", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + }, + "map-obj": { + "version": "1.0.1", + "from": "map-obj@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + }, + "map-stream": { + "version": "0.1.0", + "from": "map-stream@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz" + }, + "meow": { + "version": "3.7.0", + "from": "meow@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "dependencies": { + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + } + } + }, + "mime-db": { + "version": "1.22.0", + "from": "mime-db@>=1.22.0 <1.23.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" + }, + "mime-types": { + "version": "2.1.10", + "from": "mime-types@>=2.1.7 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" + }, + "minimatch": { + "version": "2.0.10", + "from": "minimatch@>=2.0.3 <3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" + }, + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "mousetrap": { + "version": "1.5.3", + "from": "mousetrap@>=1.5.3 <2.0.0", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.5.3.tgz" + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "multimatch": { + "version": "2.0.0", + "from": "multimatch@2.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.0.0.tgz" + }, + "multipipe": { + "version": "0.1.2", + "from": "multipipe@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz" + }, + "nan": { + "version": "2.2.0", + "from": "nan@>=2.0.8 <3.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.0.tgz" + }, + "node-gyp": { + "version": "3.3.1", + "from": "node-gyp@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.3.1.tgz", + "dependencies": { + "glob": { + "version": "4.5.3", + "from": "glob@>=3.0.0 <4.0.0||>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "dependencies": { + "minimatch": { + "version": "2.0.10", + "from": "minimatch@>=2.0.1 <3.0.0" + } + } + }, + "minimatch": { + "version": "1.0.0", + "from": "minimatch@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz" + } + } + }, + "node-sass": { + "version": "3.4.2", + "from": "node-sass@>=3.4.1 <4.0.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-3.4.2.tgz" + }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@>=1.4.7 <1.5.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" + }, + "nopt": { + "version": "3.0.6", + "from": "nopt@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" + }, + "normalize-package-data": { + "version": "2.3.5", + "from": "normalize-package-data@>=2.3.4 <3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz" + }, + "normalize-range": { + "version": "0.1.2", + "from": "normalize-range@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" + }, + "npmconf": { + "version": "2.1.2", + "from": "npmconf@>=2.1.2 <3.0.0", + "resolved": "https://registry.npmjs.org/npmconf/-/npmconf-2.1.2.tgz" + }, + "npmlog": { + "version": "2.0.3", + "from": "npmlog@>=0.0.0 <1.0.0||>=1.0.0 <2.0.0||>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.3.tgz" + }, + "num2fraction": { + "version": "1.2.2", + "from": "num2fraction@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz" + }, + "number-is-nan": { + "version": "1.0.0", + "from": "number-is-nan@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" + }, + "oauth-sign": { + "version": "0.8.1", + "from": "oauth-sign@>=0.8.0 <0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz" + }, + "object-assign": { + "version": "3.0.0", + "from": "object-assign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz" + }, + "once": { + "version": "1.3.3", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" + }, + "orchestrator": { + "version": "0.3.7", + "from": "orchestrator@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.7.tgz" + }, + "ordered-read-streams": { + "version": "0.1.0", + "from": "ordered-read-streams@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz" + }, + "os-homedir": { + "version": "1.0.1", + "from": "os-homedir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz" + }, + "os-locale": { + "version": "1.4.0", + "from": "os-locale@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz" + }, + "os-shim": { + "version": "0.1.3", + "from": "os-shim@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz" + }, + "os-tmpdir": { + "version": "1.0.1", + "from": "os-tmpdir@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" + }, + "osenv": { + "version": "0.1.3", + "from": "osenv@>=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.3.tgz" + }, + "parse-json": { + "version": "2.2.0", + "from": "parse-json@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz" + }, + "path-array": { + "version": "1.0.1", + "from": "path-array@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-array/-/path-array-1.0.1.tgz" + }, + "path-exists": { + "version": "1.0.0", + "from": "path-exists@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz" + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + }, + "path-type": { + "version": "1.1.0", + "from": "path-type@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" + }, + "pause-stream": { + "version": "0.0.11", + "from": "pause-stream@0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz" + }, + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + }, + "pinkie": { + "version": "2.0.4", + "from": "pinkie@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + }, + "pinkie-promise": { + "version": "2.0.0", + "from": "pinkie-promise@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz" + }, + "postcss": { + "version": "5.0.19", + "from": "postcss@>=5.0.4 <6.0.0", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.0.19.tgz", + "dependencies": { + "supports-color": { + "version": "3.1.2", + "from": "supports-color@>=3.1.2 <4.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz" + } + } + }, + "postcss-value-parser": { + "version": "3.3.0", + "from": "postcss-value-parser@>=3.2.3 <4.0.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz" + }, + "pretty-hrtime": { + "version": "1.0.2", + "from": "pretty-hrtime@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.2.tgz" + }, + "private": { + "version": "0.1.6", + "from": "private@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz" + }, + "process-nextick-args": { + "version": "1.0.6", + "from": "process-nextick-args@>=1.0.6 <1.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz" + }, + "proto-list": { + "version": "1.2.4", + "from": "proto-list@>=1.2.1 <1.3.0", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz" + }, + "pseudomap": { + "version": "1.0.2", + "from": "pseudomap@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" + }, + "qs": { + "version": "6.0.2", + "from": "qs@>=6.0.2 <6.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.2.tgz" + }, + "read-pkg": { + "version": "1.1.0", + "from": "read-pkg@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz" + }, + "read-pkg-up": { + "version": "1.0.1", + "from": "read-pkg-up@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz" + }, + "readable-stream": { + "version": "1.1.13", + "from": "readable-stream@>=1.1.9 <1.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz" + }, + "rechoir": { + "version": "0.6.2", + "from": "rechoir@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" + }, + "redent": { + "version": "1.0.0", + "from": "redent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz" + }, + "regenerate": { + "version": "1.2.1", + "from": "regenerate@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.2.1.tgz" + }, + "regexpu-core": { + "version": "1.0.0", + "from": "regexpu-core@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz" + }, + "regjsgen": { + "version": "0.2.0", + "from": "regjsgen@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz" + }, + "regjsparser": { + "version": "0.1.5", + "from": "regjsparser@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz" + }, + "repeat-string": { + "version": "1.5.4", + "from": "repeat-string@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.4.tgz" + }, + "repeating": { + "version": "1.1.3", + "from": "repeating@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz" + }, + "replace-ext": { + "version": "0.0.1", + "from": "replace-ext@0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz" + }, + "request": { + "version": "2.69.0", + "from": "request@>=2.61.0 <3.0.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz", + "dependencies": { + "extend": { + "version": "3.0.0", + "from": "extend@>=3.0.0 <3.1.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" + } + } + }, + "resolve": { + "version": "1.1.7", + "from": "resolve@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + }, + "right-align": { + "version": "0.1.3", + "from": "right-align@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz" + }, + "rimraf": { + "version": "2.5.2", + "from": "rimraf@>=2.4.3 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", + "dependencies": { + "glob": { + "version": "7.0.3", + "from": "glob@>=7.0.0 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz" + } + } + }, + "sass-graph": { + "version": "2.1.1", + "from": "sass-graph@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.1.1.tgz", + "dependencies": { + "glob": { + "version": "6.0.4", + "from": "glob@>=6.0.4 <7.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz" + }, + "lodash": { + "version": "4.6.1", + "from": "lodash@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.6.1.tgz" + } + } + }, + "semver": { + "version": "4.3.6", + "from": "semver@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz" + }, + "sequencify": { + "version": "0.0.7", + "from": "sequencify@>=0.0.7 <0.1.0", + "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz" + }, + "shebang-regex": { + "version": "1.0.0", + "from": "shebang-regex@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + }, + "sigmund": { + "version": "1.0.1", + "from": "sigmund@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + }, + "signal-exit": { + "version": "2.1.2", + "from": "signal-exit@>=2.1.2 <3.0.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz" + }, + "slash": { + "version": "1.0.0", + "from": "slash@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, + "source-map": { + "version": "0.5.3", + "from": "source-map@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz" + }, + "source-map-support": { + "version": "0.2.10", + "from": "source-map-support@>=0.2.10 <0.3.0", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz", + "dependencies": { + "source-map": { + "version": "0.1.32", + "from": "source-map@0.1.32", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz" + } + } + }, + "sparkles": { + "version": "1.0.0", + "from": "sparkles@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz" + }, + "spawn-sync": { + "version": "1.0.15", + "from": "spawn-sync@>=1.0.15 <2.0.0", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz" + }, + "spdx-correct": { + "version": "1.0.2", + "from": "spdx-correct@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz" + }, + "spdx-exceptions": { + "version": "1.0.4", + "from": "spdx-exceptions@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz" + }, + "spdx-expression-parse": { + "version": "1.0.2", + "from": "spdx-expression-parse@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz" + }, + "spdx-license-ids": { + "version": "1.2.0", + "from": "spdx-license-ids@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.0.tgz" + }, + "split": { + "version": "0.3.3", + "from": "split@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz" + }, + "sshpk": { + "version": "1.7.4", + "from": "sshpk@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz" + }, + "stream-combiner": { + "version": "0.0.4", + "from": "stream-combiner@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz" + }, + "stream-consume": { + "version": "0.1.0", + "from": "stream-consume@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "string-width": { + "version": "1.0.1", + "from": "string-width@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz" + }, + "stringstream": { + "version": "0.0.5", + "from": "stringstream@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + }, + "strip-bom": { + "version": "2.0.0", + "from": "strip-bom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" + }, + "strip-indent": { + "version": "1.0.1", + "from": "strip-indent@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz" + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + }, + "tar": { + "version": "2.2.1", + "from": "tar@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" + }, + "through": { + "version": "2.3.8", + "from": "through@>=2.3.1 <2.4.0", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "through2": { + "version": "2.0.1", + "from": "through2@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + } + }, + "tildify": { + "version": "1.1.2", + "from": "tildify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.1.2.tgz" + }, + "time-stamp": { + "version": "1.0.0", + "from": "time-stamp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.0.0.tgz" + }, + "to-fast-properties": { + "version": "1.0.1", + "from": "to-fast-properties@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.1.tgz" + }, + "tough-cookie": { + "version": "2.2.2", + "from": "tough-cookie@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" + }, + "trim-newlines": { + "version": "1.0.0", + "from": "trim-newlines@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" + }, + "trim-right": { + "version": "1.0.1", + "from": "trim-right@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + }, + "tunnel-agent": { + "version": "0.4.2", + "from": "tunnel-agent@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz" + }, + "tweetnacl": { + "version": "0.14.1", + "from": "tweetnacl@>=0.13.0 <1.0.0", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.1.tgz" + }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.5 <0.1.0", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "uglify-js": { + "version": "2.6.2", + "from": "uglify-js@2.6.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.2.tgz", + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@>=0.2.6 <0.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" + }, + "camelcase": { + "version": "1.2.1", + "from": "camelcase@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz" + }, + "cliui": { + "version": "2.1.0", + "from": "cliui@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz" + }, + "window-size": { + "version": "0.1.0", + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" + }, + "yargs": { + "version": "3.10.0", + "from": "yargs@>=3.10.0 <3.11.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz" + } + } + }, + "uglify-save-license": { + "version": "0.4.1", + "from": "uglify-save-license@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/uglify-save-license/-/uglify-save-license-0.4.1.tgz" + }, + "uglify-to-browserify": { + "version": "1.0.2", + "from": "uglify-to-browserify@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz" + }, + "uid-number": { + "version": "0.0.5", + "from": "uid-number@0.0.5", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.5.tgz" + }, + "unique-stream": { + "version": "1.0.0", + "from": "unique-stream@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz" + }, + "user-home": { + "version": "1.1.1", + "from": "user-home@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "v8flags": { + "version": "2.0.11", + "from": "v8flags@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.0.11.tgz" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "from": "validate-npm-package-license@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz" + }, + "verror": { + "version": "1.3.6", + "from": "verror@1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" + }, + "vinyl": { + "version": "0.5.3", + "from": "vinyl@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz" + }, + "vinyl-bufferstream": { + "version": "1.0.1", + "from": "vinyl-bufferstream@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz" + }, + "vinyl-fs": { + "version": "0.3.14", + "from": "vinyl-fs@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", + "dependencies": { + "clone": { + "version": "0.2.0", + "from": "clone@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz" + }, + "graceful-fs": { + "version": "3.0.8", + "from": "graceful-fs@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz" + }, + "readable-stream": { + "version": "1.0.33", + "from": "readable-stream@>=1.0.33-1 <1.1.0-0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz" + }, + "strip-bom": { + "version": "1.0.0", + "from": "strip-bom@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz" + }, + "through2": { + "version": "0.6.5", + "from": "through2@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" + }, + "vinyl": { + "version": "0.4.6", + "from": "vinyl@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz" + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "from": "vinyl-sourcemaps-apply@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz" + }, + "which": { + "version": "1.2.4", + "from": "which@>=1.2.4 <2.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz" + }, + "window-size": { + "version": "0.1.4", + "from": "window-size@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz" + }, + "wordwrap": { + "version": "0.0.2", + "from": "wordwrap@0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" + }, + "wrap-ansi": { + "version": "1.0.0", + "from": "wrap-ansi@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-1.0.0.tgz" + }, + "wrappy": { + "version": "1.0.1", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@>=4.0.0 <4.1.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "y18n": { + "version": "3.2.0", + "from": "y18n@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.0.tgz" + }, + "yallist": { + "version": "2.0.0", + "from": "yallist@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz" + }, + "yargs": { + "version": "3.32.0", + "from": "yargs@>=3.8.0 <4.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz" + } + } +} diff --git a/src/package.json b/src/package.json index 2de6f1e..2570e1c 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "Lychee", - "version": "3.0.9", + "version": "3.1.0", "description": "Self-hosted photo-management done right.", "authors": "Tobias Reich ", "license": "MIT", @@ -9,21 +9,25 @@ "type": "git", "url": "https://github.com/electerious/Lychee.git" }, - "devDependencies": { - "babel-preset-es2015": "^6.3.13", + "scripts": { + "start": "gulp watch", + "compile": "gulp" + }, + "dependencies": { + "babel-preset-es2015": "^6.6.0", "basiccontext": "^3.5.1", - "basicmodal": "^3.3.2", - "gulp": "^3.9.0", + "basicmodal": "^3.3.3", + "gulp": "^3.9.1", "gulp-autoprefixer": "3.1.0", - "gulp-babel": "^6.1.1", + "gulp-babel": "^6.1.2", "gulp-concat": "^2.6.0", "gulp-inject": "^3.0.0", "gulp-load-plugins": "^1.2.0", - "gulp-minify-css": "^1.2.3", + "gulp-minify-css": "^1.2.4", "gulp-rimraf": "^0.2.0", - "gulp-sass": "^2.1.1", - "gulp-uglify": "^1.5.1", - "jquery": "^2.1.4", + "gulp-sass": "^2.2.0", + "gulp-uglify": "^1.5.3", + "jquery": "^2.2.1", "mousetrap": "^1.5.3" } } diff --git a/src/scripts/_gup.js b/src/scripts/_gup.js index 8757da7..c71c790 100644 --- a/src/scripts/_gup.js +++ b/src/scripts/_gup.js @@ -2,9 +2,9 @@ function gup(b) { b = b.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]") - let a = "[\\?&]" + b + "=([^&#]*)", - d = new RegExp(a), - c = d.exec(window.location.href) + let a = "[\\?&]" + b + "=([^&#]*)" + let d = new RegExp(a) + let c = d.exec(window.location.href) if (c === null) return '' else return c[1] diff --git a/src/scripts/album.js b/src/scripts/album.js index b552fd6..961e8b1 100644 --- a/src/scripts/album.js +++ b/src/scripts/album.js @@ -55,7 +55,7 @@ album.load = function(albumID, refresh = false) { } else { // Album not public lychee.content.show() - lychee.goto('') + lychee.goto() } return false } @@ -110,12 +110,11 @@ album.add = function() { basicModal.close() - if (title.length===0) title = 'Untitled' + let params = { + title + } - api.post('Album::add', { title }, function(data) { - - // Avoid first album to be true - if (data===true) data = 1 + api.post('Album::add', params, function(data) { if (data!==false && isNumber(data)) { albums.refresh() @@ -146,20 +145,18 @@ album.add = function() { album.delete = function(albumIDs) { - let action = {}, - cancel = {}, - msg = '' + let action = {} + let cancel = {} + let msg = '' if (!albumIDs) return false - if (albumIDs instanceof Array===false) albumIDs = [albumIDs] + if (albumIDs instanceof Array===false) albumIDs = [ albumIDs ] action.fn = function() { - let params - basicModal.close() - params = { + let params = { albumIDs: albumIDs.join() } @@ -176,7 +173,7 @@ album.delete = function(albumIDs) { } else { albums.refresh() - lychee.goto('') + lychee.goto() } @@ -204,6 +201,9 @@ album.delete = function(albumIDs) { if (album.json) albumTitle = album.json.title else if (albums.json) albumTitle = albums.getByID(albumIDs).title + // Fallback for album without a title + if (albumTitle==='') albumTitle = 'Untitled' + msg = lychee.html`

Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!

` } else { @@ -234,11 +234,11 @@ album.delete = function(albumIDs) { album.setTitle = function(albumIDs) { - let oldTitle = '', - msg = '' + let oldTitle = '' + let msg = '' if (!albumIDs) return false - if (albumIDs instanceof Array===false) albumIDs = [albumIDs] + if (albumIDs instanceof Array===false) albumIDs = [ albumIDs ] if (albumIDs.length===1) { @@ -246,18 +246,13 @@ album.setTitle = function(albumIDs) { if (album.json) oldTitle = album.json.title else if (albums.json) oldTitle = albums.getByID(albumIDs).title - if (!oldTitle) oldTitle = '' - } const action = function(data) { - let newTitle = data.title - basicModal.close() - // Set title to Untitled when empty - newTitle = (newTitle==='') ? 'Untitled' : newTitle + let newTitle = data.title if (visible.album()) { @@ -315,7 +310,7 @@ album.setTitle = function(albumIDs) { album.setDescription = function(albumID) { - let oldDescription = album.json.description.replace(/'/g, ''') + let oldDescription = album.json.description const action = function(data) { @@ -365,12 +360,12 @@ album.setPublic = function(albumID, modal, e) { if (modal===true) { - let text = '', - action = {} + let text = '' + let action = {} action.fn = () => { - // setPublic function without showing the modal + // Call setPublic function without showing the modal album.setPublic(album.getID(), false, e) } @@ -393,11 +388,11 @@ album.setPublic = function(albumID, modal, e) {
-

Listed to visitors of your Lychee.

+

Only people with the direct link can view this album.

-

Only accessible with a valid password.

+

Album only accessible with a valid password.

@@ -433,9 +428,8 @@ album.setPublic = function(albumID, modal, e) { } }) - // Active visible by default (public = 0) - if ((album.json.public==='1' && album.json.visible==='1') || (album.json.public==='0')) $('.basicModal .choice input[name="visible"]').click() - if (album.json.downloadable==='1') $('.basicModal .choice input[name="downloadable"]').click() + if (album.json.public==='1' && album.json.visible==='0') $('.basicModal .choice input[name="hidden"]').click() + if (album.json.downloadable==='1') $('.basicModal .choice input[name="downloadable"]').click() $('.basicModal .choice input[name="password"]').on('change', function() { @@ -455,8 +449,8 @@ album.setPublic = function(albumID, modal, e) { album.json.public = '1' // Set visible - if ($('.basicModal .choice input[name="visible"]:checked').length===1) album.json.visible = '1' - else album.json.visible = '0' + if ($('.basicModal .choice input[name="hidden"]:checked').length===1) album.json.visible = '0' + else album.json.visible = '1' // Set downloadable if ($('.basicModal .choice input[name="downloadable"]:checked').length===1) album.json.downloadable = '1' @@ -484,12 +478,12 @@ album.setPublic = function(albumID, modal, e) { // Set data and refresh view if (visible.album()) { - album.json.visible = (album.json.public==='0') ? '0' : album.json.visible + album.json.visible = (album.json.public==='0') ? '1' : album.json.visible album.json.downloadable = (album.json.public==='0') ? '0' : album.json.downloadable album.json.password = (album.json.public==='0') ? '0' : album.json.password view.album.public() - view.album.visible() + view.album.hidden() view.album.downloadable() view.album.password() @@ -515,8 +509,8 @@ album.setPublic = function(albumID, modal, e) { album.share = function(service) { - let link = '', - url = location.href + let link = '' + let url = location.href switch (service) { case 'twitter': @@ -539,8 +533,8 @@ album.share = function(service) { album.getArchive = function(albumID) { - let link, - url = `${ api.path }?function=Album::getArchive&albumID=${ albumID }` + let link = '' + let url = `${ api.path }?function=Album::getArchive&albumID=${ albumID }` if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url) else link = location.href.replace(location.hash, '') + url @@ -553,26 +547,26 @@ album.getArchive = function(albumID) { album.merge = function(albumIDs) { - let title = '', - sTitle = '', - msg = '' + let title = '' + let sTitle = '' + let msg = '' if (!albumIDs) return false - if (albumIDs instanceof Array===false) albumIDs = [albumIDs] + if (albumIDs instanceof Array===false) albumIDs = [ albumIDs ] // Get title of first album if (albums.json) title = albums.getByID(albumIDs[0]).title - if (!title) title = '' - title = title.replace(/'/g, ''') + // Fallback for first album without a title + if (title==='') title = 'Untitled' if (albumIDs.length===2) { // Get title of second album if (albums.json) sTitle = albums.getByID(albumIDs[1]).title - if (!sTitle) sTitle = '' - sTitle = sTitle.replace(/'/g, ''') + // Fallback for second album without a title + if (sTitle==='') sTitle = 'Untitled' msg = lychee.html`

Are you sure you want to merge the album '$${ sTitle }' into the album '$${ title }'?

` @@ -596,7 +590,7 @@ album.merge = function(albumIDs) { lychee.error(null, params, data) } else { albums.refresh() - albums.load() + lychee.goto() } }) diff --git a/src/scripts/albums.js b/src/scripts/albums.js index ef6b6d1..26f8ea7 100644 --- a/src/scripts/albums.js +++ b/src/scripts/albums.js @@ -17,7 +17,7 @@ albums.load = function() { if (albums.json===null) { - api.post('Album::getAll', {}, function(data) { + api.post('Albums::get', {}, function(data) { let waitTime = 0 @@ -134,7 +134,7 @@ albums.deleteByID = function(albumID) { if (!albums.json) return false if (!albums.json.albums) return false - var deleted = false + let deleted = false $.each(albums.json.albums, function(i) { diff --git a/src/scripts/api.js b/src/scripts/api.js index 3301dbd..0cbfc15 100644 --- a/src/scripts/api.js +++ b/src/scripts/api.js @@ -5,7 +5,7 @@ api = { - path : 'php/api.php', + path : 'php/index.php', onError : null } @@ -14,11 +14,11 @@ api.post = function(fn, params, callback) { loadingBar.show() - params = $.extend({function: fn}, params) + params = $.extend({ function: fn }, params) const success = (data) => { - setTimeout(() => loadingBar.hide(), 100) + setTimeout(loadingBar.hide, 100) // Catch errors if (typeof data==='string' && data.substring(0, 7)==='Error: ') { @@ -26,18 +26,6 @@ api.post = function(fn, params, callback) { return false } - // Convert 1 to true and an empty string to false - if (data==='1') data = true - else if (data==='') data = false - - // Convert to JSON if string start with '{' and ends with '}' - if (typeof data==='string' && - data.substring(0, 1)==='{' && - data.substring(data.length-1, data.length)==='}') data = $.parseJSON(data) - - // Output response when debug mode is enabled - if (lychee.debugMode) console.log(data) - callback(data) } @@ -52,7 +40,7 @@ api.post = function(fn, params, callback) { type: 'POST', url: api.path, data: params, - dataType: 'text', + dataType: 'json', success, error }) diff --git a/src/scripts/build.js b/src/scripts/build.js index 75cc311..513f39b 100644 --- a/src/scripts/build.js +++ b/src/scripts/build.js @@ -115,16 +115,16 @@ build.photo = function(data) { build.imageview = function(data, visibleControls) { - let html = '', - hasMedium = data.medium!=='' + let html = '' + let hasMedium = data.medium!=='' if (hasMedium===false) { - html += lychee.html`
` + html += lychee.html`` } else { - html += lychee.html`
` + html += lychee.html`` } @@ -182,17 +182,12 @@ build.uploadModal = function(title, files) { let file = files[i] - if (file.name.length>40) file.name = file.name.substr(0, 17) + '...' + file.name.substr(file.name.length-20, 20) + if (file.name.length>40) file.name = file.name.substr(0, 17) + '...' + file.name.substr(file.name.length - 20, 20) html += lychee.html`
$${ file.name } - ` - - if (file.supported===true) html += `` - else html += `Not supported` - - html += ` +

` diff --git a/src/scripts/contextMenu.js b/src/scripts/contextMenu.js index 2d6b6b5..1782cc0 100644 --- a/src/scripts/contextMenu.js +++ b/src/scripts/contextMenu.js @@ -31,8 +31,8 @@ contextMenu.settings = function(e) { { title: build.iconic('dropbox', 'ionicons') + 'Set Dropbox', fn: settings.setDropboxKey }, { }, { title: build.iconic('info') + 'About Lychee', fn: () => window.open(lychee.website) }, - { title: build.iconic('wrench') + 'Diagnostics', fn: () => window.open('plugins/check/') }, - { title: build.iconic('align-left') + 'Show Log', fn: () => window.open('plugins/displaylog/') }, + { title: build.iconic('wrench') + 'Diagnostics', fn: () => window.open('plugins/Diagnostics/') }, + { title: build.iconic('align-left') + 'Show Log', fn: () => window.open('plugins/Log/') }, { }, { title: build.iconic('account-logout') + 'Sign Out', fn: lychee.logout } ] @@ -53,9 +53,9 @@ contextMenu.album = function(albumID, e) { let showMerge = (albums.json && albums.json.albums && Object.keys(albums.json.albums).length>1) let items = [ - { title: build.iconic('pencil') + 'Rename', fn: () => album.setTitle([albumID]) }, + { title: build.iconic('pencil') + 'Rename', fn: () => album.setTitle([ albumID ]) }, { title: build.iconic('collapse-left') + 'Merge', visible: showMerge, fn: () => { basicContext.close(); contextMenu.mergeAlbum(albumID, e) } }, - { title: build.iconic('trash') + 'Delete', fn: () => album.delete([albumID]) } + { title: build.iconic('trash') + 'Delete', fn: () => album.delete([ albumID ]) } ] $('.album[data-id="' + albumID + '"]').addClass('active') @@ -90,7 +90,7 @@ contextMenu.albumMulti = function(albumIDs, e) { contextMenu.albumTitle = function(albumID, e) { - api.post('Album::getAll', {}, function(data) { + api.post('Albums::get', {}, function(data) { let items = [] @@ -100,10 +100,14 @@ contextMenu.albumTitle = function(albumID, e) { $.each(data.albums, function() { if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg' + if (this.title==='') this.title = 'Untitled' - let title = lychee.html`
$${ this.title }
` + let html = lychee.html`
$${ this.title }
` - if (this.id!=albumID) items.push({ title, fn: () => lychee.goto(this.id) }) + if (this.id!=albumID) items.push({ + title: html, + fn: () => lychee.goto(this.id) + }) }) @@ -111,7 +115,7 @@ contextMenu.albumTitle = function(albumID, e) { } - items.unshift({ title: build.iconic('pencil') + 'Rename', fn: () => album.setTitle([albumID]) }) + items.unshift({ title: build.iconic('pencil') + 'Rename', fn: () => album.setTitle([ albumID ]) }) basicContext.show(items, e.originalEvent, contextMenu.close) @@ -121,7 +125,7 @@ contextMenu.albumTitle = function(albumID, e) { contextMenu.mergeAlbum = function(albumID, e) { - api.post('Album::getAll', {}, function(data) { + api.post('Albums::get', {}, function(data) { let items = [] @@ -130,10 +134,14 @@ contextMenu.mergeAlbum = function(albumID, e) { $.each(data.albums, function() { if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg' + if (this.title==='') this.title = 'Untitled' - let title = lychee.html`
$${ this.title }
` + let html = lychee.html`
$${ this.title }
` - if (this.id!=albumID) items.push({ title, fn: () => album.merge([albumID, this.id]) }) + if (this.id!=albumID) items.push({ + title: html, + fn: () => album.merge([ albumID, this.id ]) + }) }) @@ -154,13 +162,13 @@ contextMenu.photo = function(photoID, e) { // in order to keep the selection let items = [ - { title: build.iconic('star') + 'Star', fn: () => photo.setStar([photoID]) }, - { title: build.iconic('tag') + 'Tags', fn: () => photo.editTags([photoID]) }, + { title: build.iconic('star') + 'Star', fn: () => photo.setStar([ photoID ]) }, + { title: build.iconic('tag') + 'Tags', fn: () => photo.editTags([ photoID ]) }, { }, - { title: build.iconic('pencil') + 'Rename', fn: () => photo.setTitle([photoID]) }, - { title: build.iconic('layers') + 'Duplicate', fn: () => photo.duplicate([photoID]) }, - { title: build.iconic('folder') + 'Move', fn: () => { basicContext.close(); contextMenu.move([photoID], e) } }, - { title: build.iconic('trash') + 'Delete', fn: () => photo.delete([photoID]) } + { title: build.iconic('pencil') + 'Rename', fn: () => photo.setTitle([ photoID ]) }, + { title: build.iconic('layers') + 'Duplicate', fn: () => photo.duplicate([ photoID ]) }, + { title: build.iconic('folder') + 'Move', fn: () => { basicContext.close(); contextMenu.move([ photoID ], e) } }, + { title: build.iconic('trash') + 'Delete', fn: () => photo.delete([ photoID ]) } ] $('.photo[data-id="' + photoID + '"]').addClass('active') @@ -194,7 +202,7 @@ contextMenu.photoMulti = function(photoIDs, e) { contextMenu.photoTitle = function(albumID, photoID, e) { let items = [ - { title: build.iconic('pencil') + 'Rename', fn: () => photo.setTitle([photoID]) } + { title: build.iconic('pencil') + 'Rename', fn: () => photo.setTitle([ photoID ]) } ] let data = album.json @@ -206,9 +214,14 @@ contextMenu.photoTitle = function(albumID, photoID, e) { // Generate list of albums $.each(data.content, function(index) { - let title = lychee.html`
$${ this.title }
` + if (this.title==='') this.title = 'Untitled' - if (this.id!=photoID) items.push({ title, fn: () => lychee.goto(albumID + '/' + this.id) }) + let html = lychee.html`
$${ this.title }
` + + if (this.id!=photoID) items.push({ + title: html, + fn: () => lychee.goto(albumID + '/' + this.id) + }) }) @@ -236,9 +249,9 @@ contextMenu.photoMore = function(photoID, e) { contextMenu.move = function(photoIDs, e) { - var items = [] + let items = [] - api.post('Album::getAll', {}, function(data) { + api.post('Albums::get', {}, function(data) { if (data.num===0) { @@ -253,10 +266,14 @@ contextMenu.move = function(photoIDs, e) { $.each(data.albums, function() { if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg' + if (this.title==='') this.title = 'Untitled' - let title = lychee.html`
$${ this.title }
` + let html = lychee.html`
$${ this.title }
` - if (this.id!=album.getID()) items.push({ title, fn: () => photo.setAlbum(photoIDs, this.id) }) + if (this.id!=album.getID()) items.push({ + title: html, + fn: () => photo.setAlbum(photoIDs, this.id) + }) }) @@ -278,8 +295,8 @@ contextMenu.move = function(photoIDs, e) { contextMenu.sharePhoto = function(photoID, e) { - let link = photo.getViewLink(photoID), - iconClass = 'ionicons' + let link = photo.getViewLink(photoID) + let iconClass = 'ionicons' let items = [ { title: ``, fn: () => {}, class: 'basicContext__item--noHover' }, diff --git a/src/scripts/header.js b/src/scripts/header.js index bced47a..6c4b81c 100644 --- a/src/scripts/header.js +++ b/src/scripts/header.js @@ -46,17 +46,17 @@ header.bind = function() { header.dom('#button_info') .on(eventName, sidebar.toggle) header.dom('.button_add') .on(eventName, contextMenu.add) header.dom('#button_more') .on(eventName, function(e) { contextMenu.photoMore(photo.getID(), e) }) - header.dom('#button_move') .on(eventName, function(e) { contextMenu.move([photo.getID()], e) }) + header.dom('#button_move') .on(eventName, function(e) { contextMenu.move([ photo.getID() ], e) }) header.dom('.header__hostedwith') .on(eventName, function() { window.open(lychee.website) }) - header.dom('#button_trash_album') .on(eventName, function() { album.delete([album.getID()]) }) - header.dom('#button_trash') .on(eventName, function() { photo.delete([photo.getID()]) }) + header.dom('#button_trash_album') .on(eventName, function() { album.delete([ album.getID() ]) }) + header.dom('#button_trash') .on(eventName, function() { photo.delete([ photo.getID() ]) }) header.dom('#button_archive') .on(eventName, function() { album.getArchive(album.getID()) }) - header.dom('#button_star') .on(eventName, function() { photo.setStar([photo.getID()]) }) - header.dom('#button_back_home') .on(eventName, function() { lychee.goto('') }) + header.dom('#button_star') .on(eventName, function() { photo.setStar([ photo.getID() ]) }) + header.dom('#button_back_home') .on(eventName, function() { lychee.goto() }) header.dom('#button_back') .on(eventName, function() { lychee.goto(album.getID()) }) header.dom('.header__search').on('keyup click', function() { search.find($(this).val()) }) - header.dom('.header__clear').on(eventName, function () { + header.dom('.header__clear').on(eventName, function() { header.dom('.header__search').focus() search.reset() }) @@ -99,8 +99,8 @@ header.hide = function(e, delay = 500) { header.setTitle = function(title = 'Untitled') { - let $title = header.dom('.header__title'), - html = lychee.html`$${ title }${ build.iconic('caret-bottom') }` + let $title = header.dom('.header__title') + let html = lychee.html`$${ title }${ build.iconic('caret-bottom') }` $title.html(html) diff --git a/src/scripts/init.js b/src/scripts/init.js index 474064d..71520e5 100755 --- a/src/scripts/init.js +++ b/src/scripts/init.js @@ -24,38 +24,38 @@ $(document).ready(function() { // Keyboard Mousetrap - .bind('left', function() { + .bind([ 'left' ], function() { if (visible.photo()) { $('#imageview a#previous').click(); return false } }) - .bind('right', function() { + .bind([ 'right' ], function() { if (visible.photo()) { $('#imageview a#next').click(); return false } }) - .bind('u', function() { + .bind([ 'u' ], function() { if (!visible.photo()) { $('#upload_files').click(); return false } }) - .bind(['s', 'f'], function() { + .bind([ 's', 'f' ], function() { if (visible.photo()) { header.dom('#button_star').click(); return false } else if (visible.albums()) { header.dom('.header__search').focus(); return false } }) - .bind('r', function() { + .bind([ 'r' ], function() { if (visible.album()) { album.setTitle(album.getID()); return false } else if (visible.photo()) { photo.setTitle([photo.getID()]); return false } }) - .bind('d', function() { + .bind([ 'd' ], function() { if (visible.photo()) { photo.setDescription(photo.getID()); return false } else if (visible.album()) { album.setDescription(album.getID()); return false } }) - .bind('t', function() { + .bind([ 't' ], function() { if (visible.photo()) { photo.editTags([photo.getID()]); return false } }) - .bind('i', function() { + .bind([ 'i' ], function() { if (!visible.multiselect()) { sidebar.toggle(); return false } }) - .bind(['command+backspace', 'ctrl+backspace'], function() { + .bind([ 'command+backspace', 'ctrl+backspace' ], function() { if (visible.photo() && basicModal.visible()===false) { photo.delete([photo.getID()]); return false } else if (visible.album() && basicModal.visible()===false) { album.delete([album.getID()]); return false } }) - .bind(['command+a', 'ctrl+a'], function() { + .bind([ 'command+a', 'ctrl+a' ], function() { if (visible.album() && basicModal.visible()===false) { multiselect.selectAll(); return false } else if (visible.albums() && basicModal.visible()===false) { multiselect.selectAll(); return false } }) @@ -64,11 +64,11 @@ $(document).ready(function() { if (basicModal.visible()===true) basicModal.action() }) - Mousetrap.bindGlobal(['esc', 'command+up'], function() { + Mousetrap.bindGlobal([ 'esc', 'command+up' ], function() { if (basicModal.visible()===true) basicModal.cancel() else if (visible.contextMenu()) contextMenu.close() else if (visible.photo()) lychee.goto(album.getID()) - else if (visible.album()) lychee.goto('') + else if (visible.album()) lychee.goto() else if (visible.albums() && header.dom('.header__search').val().length!==0) search.reset() return false }) diff --git a/src/scripts/loadingBar.js b/src/scripts/loadingBar.js index f656511..732d0e5 100755 --- a/src/scripts/loadingBar.js +++ b/src/scripts/loadingBar.js @@ -29,7 +29,7 @@ loadingBar.show = function(status, errorText) { if (!errorText) errorText = 'Whoops, it looks like something went wrong. Please reload the site and try again!' // Move header down - if (visible.header()) header.dom().addClass('header__error') + if (visible.header()) header.dom().addClass('header--error') // Modify loading loadingBar.dom() @@ -56,7 +56,7 @@ loadingBar.show = function(status, errorText) { loadingBar._timeout = setTimeout(() => { // Move header down - if (visible.header()) header.dom().addClass('header__loading') + if (visible.header()) header.dom().addClass('header--loading') // Modify loading loadingBar.dom() @@ -81,7 +81,7 @@ loadingBar.hide = function(force) { loadingBar.status = null // Move header up - header.dom().removeClass('header__error header__loading') + header.dom().removeClass('header--error header--loading') // Set timeout clearTimeout(loadingBar._timeout) diff --git a/src/scripts/lychee.js b/src/scripts/lychee.js index 03d1c1f..f6f6f6a 100644 --- a/src/scripts/lychee.js +++ b/src/scripts/lychee.js @@ -6,16 +6,15 @@ lychee = { title : document.title, - version : '3.0.9', - version_code : '030009', + version : '3.1.0', + versionCode : '030100', - update_path : '//update.electerious.com/index.json', + updatePath : '//update.electerious.com/index.json', updateURL : 'https://github.com/electerious/Lychee', website : 'http://lychee.electerious.com', publicMode : false, viewMode : false, - debugMode : false, checkForUpdates : '1', sortingPhotos : '', @@ -32,11 +31,7 @@ lychee = { lychee.init = function() { - let params = { - version: lychee.version_code - } - - api.post('Session::init', params, function(data) { + api.post('Session::init', {}, function(data) { // Check status // 0 = No configuration @@ -88,8 +83,8 @@ lychee.init = function() { lychee.login = function(data) { - let user = data.username, - password = data.password + let user = data.username + let password = data.password let params = { user, @@ -100,10 +95,6 @@ lychee.login = function(data) { if (data===true) { - // Use 'try' to catch a thrown error when Safari is in private mode - try { localStorage.setItem('lychee_username', user) } - catch (err) {} - window.location.reload() } else { @@ -121,8 +112,8 @@ lychee.loginDialog = function() { let msg = lychee.html`

- - + +

Lychee $${ lychee.version }Update available!

` @@ -141,14 +132,6 @@ lychee.loginDialog = function() { } }) - if (localStorage) { - let localUsername = localStorage.getItem('lychee_username') - if (localUsername!=null && localUsername.length>0) { - $('.basicModal input[name="username"]').val(localUsername) - $('.basicModal input[name="password"]').focus() - } - } - if (lychee.checkForUpdates==='1') lychee.getUpdate() } @@ -161,10 +144,9 @@ lychee.logout = function() { } -lychee.goto = function(url) { +lychee.goto = function(url = '') { - if (url===undefined) url = '#' - else url = '#' + url + url = '#' + url history.pushState(null, null, url) lychee.load() @@ -173,9 +155,9 @@ lychee.goto = function(url) { lychee.load = function() { - let albumID = '', - photoID = '', - hash = document.location.hash.replace('#', '').split('/') + let albumID = '' + let photoID = '' + let hash = document.location.hash.replace('#', '').split('/') $('.no_content').remove() contextMenu.close() @@ -223,6 +205,7 @@ lychee.load = function() { // Show Albums if (visible.photo()) view.photo.hide() + lychee.content.show() albums.load() } @@ -231,9 +214,13 @@ lychee.load = function() { lychee.getUpdate = function() { + const success = function(data) { + if (data.lychee.version>parseInt(lychee.versionCode)) $('.version span').show() + } + $.ajax({ - url : lychee.update_path, - success : function(data) { if (data.lychee.version>parseInt(lychee.version_code)) $('.version span').show() }, + url : lychee.updatePath, + success : success }) } @@ -260,14 +247,14 @@ lychee.setMode = function(mode) { .off('drop') Mousetrap - .unbind('u') - .unbind('s') - .unbind('f') - .unbind('r') - .unbind('d') - .unbind('t') - .unbind(['command+backspace', 'ctrl+backspace']) - .unbind(['command+a', 'ctrl+a']) + .unbind([ 'u' ]) + .unbind([ 's' ]) + .unbind([ 'f' ]) + .unbind([ 'r' ]) + .unbind([ 'd' ]) + .unbind([ 't' ]) + .unbind([ 'command+backspace', 'ctrl+backspace' ]) + .unbind([ 'command+a', 'ctrl+a' ]) if (mode==='public') { @@ -275,7 +262,8 @@ lychee.setMode = function(mode) { } else if (mode==='view') { - Mousetrap.unbind(['esc', 'command+up']) + Mousetrap.unbind([ 'esc', 'command+up' ]) + $('#button_back, a#next, a#previous').remove() $('.no_content').remove() @@ -289,8 +277,8 @@ lychee.setMode = function(mode) { lychee.animate = function(obj, animation) { let animations = [ - ['fadeIn', 'fadeOut'], - ['contentZoomIn', 'contentZoomOut'] + [ 'fadeIn', 'fadeOut' ], + [ 'contentZoomIn', 'contentZoomOut' ] ] if (!obj.jQuery) obj = $(obj) @@ -310,8 +298,8 @@ lychee.animate = function(obj, animation) { lychee.retinize = function(path = '') { - let extention = path.split('.').pop(), - isPhoto = extention!=='svg' + let extention = path.split('.').pop() + let isPhoto = extention!=='svg' if (isPhoto===true) { @@ -329,12 +317,12 @@ lychee.retinize = function(path = '') { lychee.loadDropbox = function(callback) { - if (!lychee.dropbox && lychee.dropboxKey) { + if (lychee.dropbox===false && lychee.dropboxKey!=null && lychee.dropboxKey!=='') { loadingBar.show() - let g = document.createElement('script'), - s = document.getElementsByTagName('script')[0] + let g = document.createElement('script') + let s = document.getElementsByTagName('script')[0] g.src = 'https://www.dropbox.com/static/api/1/dropins.js' g.id = 'dropboxjs' @@ -350,7 +338,7 @@ lychee.loadDropbox = function(callback) { } s.parentNode.insertBefore(g, s) - } else if (lychee.dropbox&&lychee.dropboxKey) { + } else if (lychee.dropbox===true && lychee.dropboxKey!=null && lychee.dropboxKey!=='') { callback() @@ -364,8 +352,8 @@ lychee.loadDropbox = function(callback) { lychee.getEventName = function() { - let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement), - eventName = (touchendSupport===true ? 'touchend' : 'click') + let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement) + let eventName = (touchendSupport===true ? 'touchend' : 'click') return eventName @@ -392,8 +380,8 @@ lychee.html = function(literalSections, ...substs) { // Use raw literal sections: we don’t want // backslashes (\n etc.) to be interpreted - let raw = literalSections.raw, - result = '' + let raw = literalSections.raw + let result = '' substs.forEach((subst, i) => { @@ -416,7 +404,7 @@ lychee.html = function(literalSections, ...substs) { // Take care of last literal section // (Never fails, because an empty template string // produces one literal section, an empty string) - result += raw[raw.length-1] + result += raw[raw.length - 1] return result diff --git a/src/scripts/multiselect.js b/src/scripts/multiselect.js index b9318fe..e9c60e2 100644 --- a/src/scripts/multiselect.js +++ b/src/scripts/multiselect.js @@ -89,8 +89,8 @@ multiselect.resize = function(e) { multiselect.position.bottom === null || multiselect.position.left === null) return false - let newSize = {}, - documentSize = {} + let newSize = {} + let documentSize = {} // Get the position of the mouse let mousePos = { @@ -114,7 +114,7 @@ multiselect.resize = function(e) { // Do not leave the screen newSize.height = mousePos.y - multiselect.position.top - if ((multiselect.position.top+newSize.height)>=documentSize.height) { + if ((multiselect.position.top + newSize.height)>=documentSize.height) { newSize.height -= (multiselect.position.top + newSize.height) - documentSize.height + 2 } @@ -136,7 +136,7 @@ multiselect.resize = function(e) { // Do not leave the screen newSize.width = mousePos.x - multiselect.position.left - if ((multiselect.position.left+newSize.width)>=documentSize.width) { + if ((multiselect.position.left + newSize.width)>=documentSize.width) { newSize.width -= (multiselect.position.left + newSize.width) - documentSize.width + 2 } @@ -167,8 +167,8 @@ multiselect.getSize = function() { if (!visible.multiselect()) return false - let $elem = $('#multiselect'), - offset = $elem.offset() + let $elem = $('#multiselect') + let offset = $elem.offset() return { top : offset.top, @@ -181,9 +181,9 @@ multiselect.getSize = function() { multiselect.getSelection = function(e) { - let tolerance = 150, - ids = [], - size = multiselect.getSize() + let tolerance = 150 + let ids = [] + let size = multiselect.getSize() if (visible.contextMenu()) return false if (!visible.multiselect()) return false @@ -192,19 +192,19 @@ multiselect.getSelection = function(e) { let offset = $(this).offset() - if (offset.top>=(size.top-tolerance) && - offset.left>=(size.left-tolerance) && - (offset.top+206)<=(size.top+size.height+tolerance) && - (offset.left+206)<=(size.left+size.width+tolerance)) { + if (offset.top>=(size.top - tolerance) && + offset.left>=(size.left - tolerance) && + (offset.top + 206)<=(size.top + size.height + tolerance) && + (offset.left + 206)<=(size.left + size.width + tolerance)) { - let id = $(this).data('id') + let id = $(this).data('id') - if (id!=='0' && id!==0 && id!=='f' && id!=='s' && id!=='r' && id!=null) { + if (id!=='0' && id!==0 && id!=='f' && id!=='s' && id!=='r' && id!=null) { - ids.push(id) - $(this).addClass('active') + ids.push(id) + $(this).addClass('active') - } + } } diff --git a/src/scripts/password.js b/src/scripts/password.js index 6b28601..4fc110a 100644 --- a/src/scripts/password.js +++ b/src/scripts/password.js @@ -9,7 +9,7 @@ password = { } -password.get = function(albumID, callback, passwd) { +password.get = function(albumID, callback) { if (lychee.publicMode===false) callback() else if (album.json && album.json.password==='0') callback() @@ -17,17 +17,25 @@ password.get = function(albumID, callback, passwd) { else if (!albums.json && !album.json) { // Continue without password + album.json = { password: true } callback('') - } else if (passwd==null) { - - // Request password - password.getDialog(albumID, callback) - } else { - // Check password + // Request password + + password.getDialog(albumID, callback) + + } + +} + +password.getDialog = function(albumID, callback) { + + const action = (data) => { + + let passwd = data.password let params = { albumID, @@ -48,16 +56,10 @@ password.get = function(albumID, callback, passwd) { } -} - -password.getDialog = function(albumID, callback) { - - const action = (data) => password.get(albumID, callback, data.password) - const cancel = () => { basicModal.close() - if (visible.albums()===false) lychee.goto() + if (!visible.albums()) lychee.goto() } diff --git a/src/scripts/photo.js b/src/scripts/photo.js index c486313..a758948 100644 --- a/src/scripts/photo.js +++ b/src/scripts/photo.js @@ -24,6 +24,21 @@ photo.getID = function() { photo.load = function(photoID, albumID) { + const checkContent = function() { + if (album.json!=null) photo.load(photoID, albumID) + else setTimeout(checkContent, 100) + } + + const checkPasswd = function() { + if (password.value!=='') photo.load(photoID, albumID) + else setTimeout(checkPasswd, 200) + } + + if (album.json==null) { + checkContent() + return false + } + let params = { photoID, albumID, @@ -32,14 +47,9 @@ photo.load = function(photoID, albumID) { api.post('Photo::get', params, function(data) { - const checkPasswd = function() { - if (password.value!=='') photo.load(photoID, albumID) - else setTimeout(checkPasswd, 250) - } - if (data==='Warning: Photo private!') { lychee.content.show() - lychee.goto('') + lychee.goto() return false } @@ -71,8 +81,8 @@ photo.preloadNext = function(photoID) { album.json.content[photoID] && album.json.content[photoID].nextPhoto!='') { - let nextPhoto = album.json.content[photoID].nextPhoto, - url = album.json.content[nextPhoto].url + let nextPhoto = album.json.content[photoID].nextPhoto + let url = album.json.content[nextPhoto].url $('head [data-prefetch]').remove() $('head').append(``) @@ -152,7 +162,7 @@ photo.next = function(animate) { photo.duplicate = function(photoIDs) { if (!photoIDs) return false - if (photoIDs instanceof Array===false) photoIDs = [photoIDs] + if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ] albums.refresh() @@ -171,13 +181,13 @@ photo.duplicate = function(photoIDs) { photo.delete = function(photoIDs) { - let action = {}, - cancel = {}, - msg = '', - photoTitle = '' + let action = {} + let cancel = {} + let msg = '' + let photoTitle = '' if (!photoIDs) return false - if (photoIDs instanceof Array===false) photoIDs = [photoIDs] + if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ] if (photoIDs.length===1) { @@ -192,8 +202,8 @@ photo.delete = function(photoIDs) { action.fn = function() { - let nextPhoto, - previousPhoto + let nextPhoto = null + let previousPhoto = null basicModal.close() @@ -219,8 +229,8 @@ photo.delete = function(photoIDs) { // Go to next photo if there is a next photo and // next photo is not the current one. Show album otherwise. - if (visible.photo() && nextPhoto!=='' && nextPhoto!==photo.getID()) lychee.goto(album.getID() + '/' + nextPhoto) - else if (!visible.albums()) lychee.goto(album.getID()) + if (visible.photo() && nextPhoto!=null && nextPhoto!==photo.getID()) lychee.goto(album.getID() + '/' + nextPhoto) + else if (!visible.albums()) lychee.goto(album.getID()) let params = { photoIDs: photoIDs.join() @@ -269,11 +279,11 @@ photo.delete = function(photoIDs) { photo.setTitle = function(photoIDs) { - let oldTitle = '', - msg = '' + let oldTitle = '' + let msg = '' if (!photoIDs) return false - if (photoIDs instanceof Array===false) photoIDs = [photoIDs] + if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ] if (photoIDs.length===1) { @@ -300,8 +310,8 @@ photo.setTitle = function(photoIDs) { }) let params = { - photoIDs: photoIDs.join(), - title: newTitle + photoIDs : photoIDs.join(), + title : newTitle } api.post('Photo::setTitle', params, function(data) { @@ -335,13 +345,11 @@ photo.setTitle = function(photoIDs) { photo.setAlbum = function(photoIDs, albumID) { - let nextPhoto, - previousPhoto + let nextPhoto = null + let previousPhoto = null if (!photoIDs) return false - if (photoIDs instanceof Array===false) photoIDs = [photoIDs] - - if (visible.photo) lychee.goto(album.getID()) + if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ] photoIDs.forEach(function(id, index, array) { @@ -363,6 +371,11 @@ photo.setAlbum = function(photoIDs, albumID) { albums.refresh() + // Go to next photo if there is a next photo and + // next photo is not the current one. Show album otherwise. + if (visible.photo() && nextPhoto!=null && nextPhoto!==photo.getID()) lychee.goto(album.getID() + '/' + nextPhoto) + else if (!visible.albums()) lychee.goto(album.getID()) + let params = { photoIDs: photoIDs.join(), albumID @@ -416,7 +429,7 @@ photo.setPublic = function(photoID, e) { } basicModal.show({ - body: "

This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.

", + body: '

This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.

', buttons: { action: { title: 'Show Album', @@ -500,11 +513,11 @@ photo.setDescription = function(photoID) { photo.editTags = function(photoIDs) { - let oldTags = '', - msg = '' + let oldTags = '' + let msg = '' if (!photoIDs) return false - if (photoIDs instanceof Array===false) photoIDs = [photoIDs] + if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ] // Get tags if (visible.photo()) oldTags = photo.json.tags @@ -553,7 +566,7 @@ photo.editTags = function(photoIDs) { photo.setTags = function(photoIDs, tags) { if (!photoIDs) return false - if (photoIDs instanceof Array===false) photoIDs = [photoIDs] + if (photoIDs instanceof Array===false) photoIDs = [ photoIDs ] // Parse tags tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',') @@ -591,14 +604,14 @@ photo.deleteTag = function(photoID, index) { // Save photo.json.tags = tags.toString() - photo.setTags([photoID], photo.json.tags) + photo.setTags([ photoID ], photo.json.tags) } photo.share = function(photoID, service) { - let link = '', - url = photo.getViewLink(photoID) + let link = '' + let url = photo.getViewLink(photoID) switch (service) { case 'twitter': @@ -621,14 +634,14 @@ photo.share = function(photoID, service) { break } - if (link.length!=='') location.href = link + if (link!=='') location.href = link } photo.getArchive = function(photoID) { - let link, - url = `${ api.path }?function=Photo::getArchive&photoID=${ photoID }` + let link + let url = `${ api.path }?function=Photo::getArchive&photoID=${ photoID }` if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url) else link = location.href.replace(location.hash, '') + url diff --git a/src/scripts/search.js b/src/scripts/search.js index af7e142..216c8c8 100755 --- a/src/scripts/search.js +++ b/src/scripts/search.js @@ -19,9 +19,9 @@ search.find = function(term) { api.post('search', { term }, function(data) { - let html = '', - albumsData = '', - photosData = '' + let html = '' + let albumsData = '' + let photosData = '' // Build albums if (data && data.albums) { @@ -47,7 +47,7 @@ search.find = function(term) { if (albumsData==='' && photosData==='') html = 'error' else if (albumsData==='') html = build.divider('Photos') + photosData else if (photosData==='') html = build.divider('Albums') + albumsData - else html = build.divider('Photos') + photosData + build.divider('Albums') + albumsData + else html = build.divider('Albums') + albumsData + build.divider('Photos') + photosData // Only refresh view when search results are different if (search.hash!==data.hash) { @@ -94,7 +94,7 @@ search.reset = function() { search.hash = null lychee.animate('.divider', 'fadeOut') - albums.load() + lychee.goto() } diff --git a/src/scripts/settings.js b/src/scripts/settings.js index 876ef8b..a28f84a 100644 --- a/src/scripts/settings.js +++ b/src/scripts/settings.js @@ -9,11 +9,11 @@ settings.createConfig = function() { const action = function(data) { - let dbName = data.dbName || '', - dbUser = data.dbUser || '', - dbPassword = data.dbPassword || '', - dbHost = data.dbHost || '', - dbTablePrefix = data.dbTablePrefix || '' + let dbName = data.dbName || '' + let dbUser = data.dbUser || '' + let dbPassword = data.dbPassword || '' + let dbHost = data.dbHost || '' + let dbTablePrefix = data.dbTablePrefix || '' if (dbUser.length<1) { basicModal.error('dbUser') @@ -31,12 +31,12 @@ settings.createConfig = function() { dbTablePrefix } - api.post('Database::createConfig', params, function(data) { + api.post('Config::create', params, function(data) { if (data!==true) { // Connection failed - if (data.indexOf('Warning: Connection failed!')!==-1) { + if (data==='Warning: Connection failed!') { basicModal.show({ body: '

Unable to connect to host database because access was denied. Double-check your host, username and password and ensure that access from your current location is permitted.

', @@ -53,7 +53,7 @@ settings.createConfig = function() { } // Creation failed - if (data.indexOf('Warning: Creation failed!')!==-1) { + if (data==='Warning: Creation failed!') { basicModal.show({ body: '

Unable to create the database. Double-check your host, username and password and ensure that the specified user has the rights to modify and add content to the database.

', @@ -70,7 +70,7 @@ settings.createConfig = function() { } // Could not create file - if (data.indexOf('Warning: Could not create file!')!==-1) { + if (data==='Warning: Could not create file!') { basicModal.show({ body: "

Unable to save this configuration. Permission denied in 'data/'. Please set the read, write and execute rights for others in 'data/' and 'uploads/'. Take a look at the readme for more information.

", @@ -140,8 +140,8 @@ settings.createLogin = function() { const action = function(data) { - let username = data.username, - password = data.password + let username = data.username + let password = data.password if (username.length<1) { basicModal.error('username') @@ -204,9 +204,9 @@ settings.setLogin = function() { const action = function(data) { - let oldPassword = data.oldPassword || '', - username = data.username || '', - password = data.password || '' + let oldPassword = data.oldPassword || '' + let username = data.username || '' + let password = data.password || '' if (oldPassword.length<1) { basicModal.error('oldPassword') @@ -269,8 +269,8 @@ settings.setLogin = function() { settings.setSorting = function() { - let sortingPhotos = [], - sortingAlbums = [] + let sortingPhotos = [] + let sortingAlbums = [] const action = function() { diff --git a/src/scripts/sidebar.js b/src/scripts/sidebar.js index f628506..48fb2a9 100644 --- a/src/scripts/sidebar.js +++ b/src/scripts/sidebar.js @@ -30,14 +30,14 @@ sidebar.bind = function() { // event handlers should be removed before binding a new one. // Event Name - let eventName = lychee.getEventName(); + let eventName = lychee.getEventName() sidebar .dom('#edit_title') .off(eventName) .on(eventName, function() { - if (visible.photo()) photo.setTitle([photo.getID()]) - else if (visible.album()) album.setTitle([album.getID()]) + if (visible.photo()) photo.setTitle([ photo.getID() ]) + else if (visible.album()) album.setTitle([ album.getID() ]) }) sidebar @@ -52,7 +52,7 @@ sidebar.bind = function() { .dom('#edit_tags') .off(eventName) .on(eventName, function() { - photo.editTags([photo.getID()]) + photo.editTags([ photo.getID() ]) }) sidebar @@ -114,10 +114,10 @@ sidebar.createStructure.photo = function(data) { if (data==null || data==='') return false - let editable = false, - exifHash = data.takestamp + data.make + data.model + data.shutter + data.aperture + data.focal + data.iso, - structure = {}, - _public = '' + let editable = false + let exifHash = data.takestamp + data.make + data.model + data.shutter + data.aperture + data.focal + data.iso + let structure = {} + let _public = '' // Enable editable when user logged in if (lychee.publicMode===false) editable = true @@ -220,12 +220,12 @@ sidebar.createStructure.album = function(data) { if (data==null || data==='') return false - let editable = false, - structure = {}, - _public = '', - visible = '', - downloadable = '', - password = '' + let editable = false + let structure = {} + let _public = '' + let hidden = '' + let downloadable = '' + let password = '' // Enable editable when user logged in if (lychee.publicMode===false) editable = true @@ -242,14 +242,14 @@ sidebar.createStructure.album = function(data) { } - // Set value for visible + // Set value for hidden switch (data.visible) { - case '0' : visible = 'No' + case '0' : hidden = 'Yes' break - case '1' : visible = 'Yes' + case '1' : hidden = 'No' break - default : visible = '-' + default : hidden = '-' break } @@ -301,7 +301,7 @@ sidebar.createStructure.album = function(data) { type : sidebar.types.DEFAULT, rows : [ { title: 'Public', value: _public }, - { title: 'Visible', value: visible }, + { title: 'Hidden', value: hidden }, { title: 'Downloadable', value: downloadable }, { title: 'Password', value: password } ] @@ -367,8 +367,8 @@ sidebar.render = function(structure) { let renderTags = function(section) { - let _html = '', - editable = '' + let _html = '' + let editable = '' // Add edit-icon to the value when editable if (section.editable===true) editable = build.editIcon('edit_tags') diff --git a/src/scripts/upload.js b/src/scripts/upload.js index 2d473af..46fb1d0 100755 --- a/src/scripts/upload.js +++ b/src/scripts/upload.js @@ -40,16 +40,17 @@ upload.start = { local: function(files) { - let albumID = album.getID(), - error = false, - warning = false + let albumID = album.getID() + let error = false + let warning = false const process = function(files, file) { - let formData = new FormData(), - xhr = new XMLHttpRequest(), - pre_progress = 0, - progress = 0 + let formData = new FormData() + let xhr = new XMLHttpRequest() + let pre_progress = 0 + let progress = 0 + let next_file_started = false const finish = function() { @@ -84,98 +85,83 @@ upload.start = { } - // Check if file is supported - if (file.supported===false) { - - // Skip file - if (file.next!=null) process(files, file.next) - else { - - // Look for supported files - // If zero files are supported, hide the upload after a delay - - let hasSupportedFiles = false - - for (let i = 0; i < files.length; i++) { - - if (files[i].supported===true) { - hasSupportedFiles = true - break - } - - } - - if (hasSupportedFiles===false) setTimeout(finish, 2000) - - } - - return false - - } - formData.append('function', 'Photo::add') formData.append('albumID', albumID) - formData.append('tags', '') formData.append(0, file) xhr.open('POST', api.path) xhr.onload = function() { - let wait = false, - errorText = '' + let data = null + let wait = false + let errorText = '' + + const isNumber = (n) => (!isNaN(parseFloat(n)) && isFinite(n)) + + try { + data = JSON.parse(xhr.responseText) + } catch(e) { + data = '' + } file.ready = true // Set status - if (xhr.status===200 && xhr.responseText==='1') { + if (xhr.status===200 && isNumber(data)) { // Success - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') .status') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status') .html('Finished') .addClass('success') } else { - if (xhr.responseText.substr(0, 6)==='Error:') { + if (data.substr(0, 6)==='Error:') { - errorText = xhr.responseText.substr(6) + ' Please take a look at the console of your browser for further details.' + errorText = data.substr(6) + ' Please take a look at the console of your browser for further details.' error = true // Error Status - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') .status') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status') .html('Failed') .addClass('error') - } else if (xhr.responseText.substr(0, 8)==='Warning:') { + // Throw error + if (error===true) lychee.error('Upload failed. Server returned an error!', xhr, data) - errorText = xhr.responseText.substr(8) + } else if (data.substr(0, 8)==='Warning:') { + + errorText = data.substr(8) warning = true // Warning Status - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') .status') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status') .html('Skipped') .addClass('warning') + // Throw error + if (error===true) lychee.error('Upload failed. Server returned a warning!', xhr, data) + } else { errorText = 'Server returned an unknown response. Please take a look at the console of your browser for further details.' error = true // Error Status - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') .status') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status') .html('Failed') .addClass('error') + // Throw error + if (error===true) lychee.error('Upload failed. Server returned an unkown error!', xhr, data) + } - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') p.notice') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') p.notice') .html(errorText) .show() - // Throw error - if (error===true) lychee.error('Upload failed. Server returned the status code ' + xhr.status + '!', xhr, xhr.responseText) - } // Check if there are file which are not finished @@ -206,18 +192,21 @@ upload.start = { pre_progress = progress } - if (progress>=100) { + if (progress>=100 && next_file_started===false) { // Scroll to the uploading file let scrollPos = 0 - if ((file.num+1)>4) scrollPos = (file.num + 1 - 4) * 40 + if ((file.num + 1)>4) scrollPos = (file.num + 1 - 4) * 40 $('.basicModal .rows').scrollTop(scrollPos) // Set status to processing - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') .status').html('Processing') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status').html('Processing') // Upload next file - if (file.next!=null) process(files, file.next) + if (file.next!=null) { + process(files, file.next) + next_file_started = true + } } @@ -232,21 +221,12 @@ upload.start = { for (let i = 0; i < files.length; i++) { - files[i].num = i - files[i].ready = false - files[i].supported = true + files[i].num = i + files[i].ready = false - if (i < files.length-1) files[i].next = files[i+1] + if (i < files.length-1) files[i].next = files[i + 1] else files[i].next = null - // Check if file is supported - if (files[i].type!=='image/jpeg' && files[i].type!=='image/jpg' && files[i].type!=='image/png' && files[i].type!=='image/gif') { - - files[i].ready = true - files[i].supported = false - - } - } window.onbeforeunload = function() { return 'Lychee is currently uploading!' } @@ -276,15 +256,8 @@ upload.start = { basicModal.close() - let extension = data.link.split('.').pop() - if (extension!=='jpeg' && extension!=='jpg' && extension!=='png' && extension!=='gif' && extension!=='webp') { - loadingBar.show('error', 'File format of link not supported.') - return false - } - files[0] = { - name : data.link, - supported : true + name: data.link } upload.show('Importing URL', files, function() { @@ -363,8 +336,7 @@ upload.start = { let files = [] files[0] = { - name : data.path, - supported : true + name: data.path } upload.show('Importing from server', files, function() { @@ -387,7 +359,7 @@ upload.start = { // Go back to the album overview to show the imported albums if (visible.albums()) lychee.load() - else lychee.goto('') + else lychee.goto() basicModal.close() @@ -473,14 +445,13 @@ upload.start = { links += files[i].link + ',' files[i] = { - name : files[i].link, - supported : true + name : files[i].link } } // Remove last comma - links = links.substr(0, links.length-1) + links = links.substr(0, links.length - 1) upload.show('Importing from Dropbox', files, function() { diff --git a/src/scripts/view.js b/src/scripts/view.js index 099295a..198a4fd 100644 --- a/src/scripts/view.js +++ b/src/scripts/view.js @@ -26,8 +26,8 @@ view.albums = { init: function() { - let smartData = '', - albumsData = '' + let smartData = '' + let albumsData = '' // Smart Albums if (lychee.publicMode===false) { @@ -246,10 +246,10 @@ view.album = { }, - visible: function() { + hidden: function() { - if (album.json.visible==='1') sidebar.changeAttr('visible', 'Yes') - else sidebar.changeAttr('visible', 'No') + if (album.json.visible==='1') sidebar.changeAttr('hidden', 'No') + else sidebar.changeAttr('hidden', 'Yes') }, @@ -271,8 +271,8 @@ view.album = { if ((visible.album() || !album.json.init) && !visible.photo()) { - let structure = sidebar.createStructure.album(album.json), - html = sidebar.render(structure) + let structure = sidebar.createStructure.album(album.json) + let html = sidebar.render(structure) sidebar.dom('.sidebar__wrapper').html(html) sidebar.bind() @@ -408,27 +408,33 @@ view.photo = { lychee.imageview.html(build.imageview(photo.json, visible.header())) - let $nextArrow = lychee.imageview.find('a#next'), - $previousArrow = lychee.imageview.find('a#previous'), - photoID = photo.getID(), - hasNext = album.json && album.json.content && album.json.content[photoID] && album.json.content[photoID].nextPhoto!=='', - hasPrevious = album.json && album.json.content && album.json.content[photoID] && album.json.content[photoID].previousPhoto!=='' + let $nextArrow = lychee.imageview.find('a#next') + let $previousArrow = lychee.imageview.find('a#previous') + let photoID = photo.getID() + let hasNext = album.json && album.json.content && album.json.content[photoID] && album.json.content[photoID].nextPhoto!=='' + let hasPrevious = album.json && album.json.content && album.json.content[photoID] && album.json.content[photoID].previousPhoto!=='' - if (hasNext===false || lychee.viewMode===true) { $nextArrow.hide() } - else { + if (hasNext===false || lychee.viewMode===true) { - let nextPhotoID = album.json.content[photoID].nextPhoto, - nextPhoto = album.json.content[nextPhotoID] + $nextArrow.hide() + + } else { + + let nextPhotoID = album.json.content[photoID].nextPhoto + let nextPhoto = album.json.content[nextPhotoID] $nextArrow.css('background-image', lychee.html`linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("$${ nextPhoto.thumbUrl }")`) } - if (hasPrevious===false || lychee.viewMode===true) { $previousArrow.hide() } - else { + if (hasPrevious===false || lychee.viewMode===true) { - let previousPhotoID = album.json.content[photoID].previousPhoto, - previousPhoto = album.json.content[previousPhotoID] + $previousArrow.hide() + + } else { + + let previousPhotoID = album.json.content[photoID].previousPhoto + let previousPhoto = album.json.content[previousPhotoID] $previousArrow.css('background-image', lychee.html`linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("$${ previousPhoto.thumbUrl }")`) @@ -438,8 +444,8 @@ view.photo = { sidebar: function() { - let structure = sidebar.createStructure.photo(photo.json), - html = sidebar.render(structure) + let structure = sidebar.createStructure.photo(photo.json) + let html = sidebar.render(structure) sidebar.dom('.sidebar__wrapper').html(html) sidebar.bind() diff --git a/src/scripts/view/main.js b/src/scripts/view/main.js index 5f03cbe..329892e 100644 --- a/src/scripts/view/main.js +++ b/src/scripts/view/main.js @@ -11,8 +11,8 @@ lychee.content = $('.content') lychee.getEventName = function() { - let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement), - eventName = (touchendSupport===true ? 'touchend' : 'click') + let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement) + let eventName = (touchendSupport===true ? 'touchend' : 'click') return eventName @@ -39,8 +39,8 @@ lychee.html = function(literalSections, ...substs) { // Use raw literal sections: we don’t want // backslashes (\n etc.) to be interpreted - let raw = literalSections.raw, - result = '' + let raw = literalSections.raw + let result = '' substs.forEach((subst, i) => { @@ -63,7 +63,7 @@ lychee.html = function(literalSections, ...substs) { // Take care of last literal section // (Never fails, because an empty template string // produces one literal section, an empty string) - result += raw[raw.length-1] + result += raw[raw.length - 1] return result @@ -88,7 +88,7 @@ $(document).ready(function() { // Direct Link header.dom('#button_direct').on(eventName, function() { - let link = $('#imageview #image').css('background-image').replace(/"/g,'').replace(/url\(|\)$/ig, '') + let link = $('#imageview img').attr('src').replace(/"/g,'').replace(/url\(|\)$/ig, '') window.open(link, '_newtab') }) @@ -127,8 +127,8 @@ const loadPhotoInfo = function(photoID) { imageview.addClass('fadeIn').show() // Render Sidebar - let structure = sidebar.createStructure.photo(data), - html = sidebar.render(structure) + let structure = sidebar.createStructure.photo(data) + let html = sidebar.render(structure) sidebar.dom('.sidebar__wrapper').html(html) sidebar.bind() diff --git a/src/styles/_animations.scss b/src/styles/_animations.scss index d104dd9..7f8b3d5 100755 --- a/src/styles/_animations.scss +++ b/src/styles/_animations.scss @@ -65,18 +65,6 @@ } } -// PopIn -------------------------------------------------------------- // -@keyframes popIn { - 0% { - opacity: 0; - transform: scale(0); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - // Pulse -------------------------------------------------------------- // @keyframes pulse { 0% { diff --git a/src/styles/_basicContext.custom.scss b/src/styles/_basicContext.custom.scss index 9ef3ed2..7684577 100644 --- a/src/styles/_basicContext.custom.scss +++ b/src/styles/_basicContext.custom.scss @@ -12,7 +12,6 @@ &__item { margin-bottom: 2px; font-size: 14px; - text-shadow: $shadowLight; &--separator { margin: 4px 0; diff --git a/src/styles/_content.scss b/src/styles/_content.scss index 08f3175..07ecaca 100644 --- a/src/styles/_content.scss +++ b/src/styles/_content.scss @@ -146,7 +146,6 @@ margin: 0 5px 0 0; width: 8px; height: 8px; - filter: drop-shadow(0 1px 3px black(.4)); } .album img[data-overlay='false'] + .overlay h1, @@ -185,7 +184,6 @@ fill: #fff; width: 16px; height: 16px; - filter: drop-shadow($shadowLight); } } @@ -225,7 +223,6 @@ left: 50%; padding-top: 20px; color: white(.35); - text-shadow: 0 -1px 0 black(.4); text-align: center; transform: translateX(-50%) translateY(-50%); @@ -234,7 +231,6 @@ margin: 0 0 10px; width: 50px; height: 50px; - filter: drop-shadow(0 -1px 0 black(.4)); } p { diff --git a/src/styles/_header.scss b/src/styles/_header.scss index 96f29fe..f727063 100644 --- a/src/styles/_header.scss +++ b/src/styles/_header.scss @@ -19,7 +19,7 @@ background: none; border-bottom: none; - .header__error { background-color: rgba(10, 10, 10, .99); } + &.header--error { background-color: rgba(10, 10, 10, .99); } } // Toolbars -------------------------------------------------------------- // diff --git a/src/styles/_imageview.scss b/src/styles/_imageview.scss index 31ccd57..24d59fe 100644 --- a/src/styles/_imageview.scss +++ b/src/styles/_imageview.scss @@ -21,21 +21,25 @@ right: 30px; bottom: 30px; left: 30px; - transition: top .3s, right .3s, bottom .3s, left .3s, opacity .2s, transform .2s; + margin: auto; + max-width: calc(100% - 60px); + max-height: calc(100% - 90px); + width: auto; + height: auto; + transition: top .3s, right .3s, bottom .3s, left .3s, max-width .3s, max-height .3s; will-change: transform; - display: flex; - justify-content: center; - align-items: center; - animation-name: zoomIn; animation-duration: .3s; animation-timing-function: $timingBounce; @media (max-width: 640px) { + top: 60px; right: 20px; bottom: 20px; left: 20px; + max-width: calc(100% - 40px); + max-height: calc(100% - 80px); } } @@ -44,20 +48,8 @@ right: 0; bottom: 0; left: 0; - } - - #image > div { - width: 0; - height: 0; - } - - #image img { - position: absolute; max-width: 100%; max-height: 100%; - width: auto; - height: auto; - transform: translate(-50%, -50%); } // Previous/Next Buttons -------------------------------------------------------------- // @@ -113,7 +105,6 @@ .iconic { fill: white(.8); - filter: drop-shadow(0 1px 0 black(.4)); } } diff --git a/src/styles/_message.scss b/src/styles/_message.scss index a38e8b1..f90928c 100644 --- a/src/styles/_message.scss +++ b/src/styles/_message.scss @@ -26,7 +26,6 @@ color: white(.9); font-size: 14px; text-align: left; - text-shadow: $shadow; line-height: 20px; b { @@ -56,7 +55,6 @@ padding: 13px 0 15px; background: black(.02); color: white(.5); - text-shadow: $shadow; border-top: 1px solid black(.2); box-shadow: inset 0 1px 0 white(.02); cursor: default; @@ -85,7 +83,6 @@ width: 100%; background-color: transparent; color: #fff; - text-shadow: $shadow; border: none; // Do not use rgba() for border-bottom // to avoid a blurry line in Safari on non-retina screens @@ -116,7 +113,6 @@ color: white(1); font-size: 14px; font-weight: 700; - text-shadow: $shadow; } label input { @@ -258,7 +254,6 @@ color: #fff; font-size: 16px; font-weight: bold; - text-shadow: $shadow; text-align: center; } diff --git a/src/styles/main.scss b/src/styles/main.scss index e4b9751..f4f6c66 100755 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -12,8 +12,7 @@ } // Properties -------------------------------------------------------------- // -$shadowLight : 0 -1px 0 black(.1); -$shadow : 0 -1px 0 black(.2); +$shadow: 0 -1px 0 black(.2); // Colors ------------------------------------------------------------------ // $colorBlue : #2293EC; diff --git a/view.php b/view.php index f051f21..638af87 100644 --- a/view.php +++ b/view.php @@ -25,16 +25,9 @@ # Load required files require(__DIR__ . '/php/define.php'); require(__DIR__ . '/php/autoload.php'); - require(__DIR__ . '/php/modules/misc.php'); - require(LYCHEE_CONFIG_FILE); + require(__DIR__ . '/php/helpers/getGraphHeader.php'); - # Define the table prefix - if (!isset($dbTablePrefix)) $dbTablePrefix = ''; - defineTablePrefix($dbTablePrefix); - - $database = Database::connect($dbHost, $dbUser, $dbPassword, $dbName); - - echo getGraphHeader($database, $_GET['p']); + echo getGraphHeader($_GET['p']); }