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/CONTRIBUTING.md b/CONTRIBUTING.md index cc00b9f..86eed15 100644 --- a/CONTRIBUTING.md +++ b/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/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..ae10049 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..6a0cdcc Binary files a/dist/main.js and b/dist/main.js differ diff --git a/dist/view.js b/dist/view.js index 388f4a0..6fde421 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..13803b9 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,3 +1,33 @@ +## 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) +- `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 +- `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..927198b --- /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'])); + + } + + 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'])); + + } + + 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(); + echo $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..ce3863c --- /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..4460a5c --- /dev/null +++ b/php/Modules/Album.php @@ -0,0 +1,647 @@ +albumIDs = $albumIDs; + + return true; + + } + + /** + * @return integer|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); + + // 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..60276ff --- /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..0948249 --- /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..3bf0df1 --- /dev/null +++ b/php/Modules/Import.php @@ -0,0 +1,195 @@ +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); + 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; + } + + $pathinfo = pathinfo($url); + $filename = $pathinfo['filename'] . '.' . $pathinfo['extension']; + $tmp_name = LYCHEE_DATA . $filename; + + if (@copy($url, $tmp_name)===false) { + $error = true; + Log::error(Database::get(), __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(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..ef79a95 --- /dev/null +++ b/php/Modules/Photo.php @@ -0,0 +1,1243 @@ +photoIDs = $photoIDs; + + return true; + + } + + /** + * Creats new photo(s). + * Exits on error. + * Use $returnOnError if you want to handle errors by your own. + * @return boolean Returns true when successful. + */ + 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']); + 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 true; + + } + + /** + * @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); + if ($extension===false) { + 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..01c4c10 --- /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($username, Settings::get()['username']); + $password = crypt($password, Settings::get()['password']); + + // Check login with crypted hash + if (Settings::get()['username']===$username&& + Settings::get()['password']===$password) { + $_SESSION['login'] = true; + $_SESSION['identifier'] = Settings::get()['identifier']; + return true; + } + + // No login + if ($this->noLogin()===true) return true; + + // Call plugins + Plugins::get()->activate(__METHOD__, 1, func_get_args()); + + 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..428ba92 --- /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..a215adc 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..80f42c1 --- /dev/null +++ b/php/helpers/generateID.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/php/helpers/getExtension.php b/php/helpers/getExtension.php new file mode 100644 index 0000000..21089c0 --- /dev/null +++ b/php/helpers/getExtension.php @@ -0,0 +1,13 @@ + \ 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..307a774 --- /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/package.json b/src/package.json index 2de6f1e..b69a91b 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" }, + "scripts": { + "start": "gulp watch", + "compile": "gulp" + }, "devDependencies": { - "babel-preset-es2015": "^6.3.13", + "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..109405f 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,9 +110,11 @@ album.add = function() { basicModal.close() - if (title.length===0) title = 'Untitled' + let params = { + title + } - api.post('Album::add', { title }, function(data) { + api.post('Album::add', params, function(data) { // Avoid first album to be true if (data===true) data = 1 @@ -146,20 +148,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 +176,7 @@ album.delete = function(albumIDs) { } else { albums.refresh() - lychee.goto('') + lychee.goto() } @@ -204,6 +204,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 +237,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 +249,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 +313,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 +363,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 +391,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 +431,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 +452,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 +481,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 +512,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 +536,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 +550,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 +593,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..d45a007 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: ') { @@ -27,16 +27,13 @@ api.post = function(fn, params, callback) { } // Convert 1 to true and an empty string to false - if (data==='1') data = true - else if (data==='') data = false + if (data==='true') data = true + else if (data==='false') 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) + if (typeof data==='string' && data.substring(0, 1)==='{' && data.substring(data.length - 1, data.length)==='}') { + data = $.parseJSON(data) + } callback(data) diff --git a/src/scripts/build.js b/src/scripts/build.js index 75cc311..620d36f 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,7 +182,7 @@ 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`
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..30aca55 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, @@ -161,10 +156,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 +167,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 +217,7 @@ lychee.load = function() { // Show Albums if (visible.photo()) view.photo.hide() + lychee.content.show() albums.load() } @@ -231,9 +226,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 +259,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 +274,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 +289,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 +310,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 +329,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 +350,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 +364,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 +392,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 +416,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..8fd92d6 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,7 +31,7 @@ settings.createConfig = function() { dbTablePrefix } - api.post('Database::createConfig', params, function(data) { + api.post('Config::create', params, function(data) { if (data!==true) { @@ -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..d99d6c1 100755 --- a/src/scripts/upload.js +++ b/src/scripts/upload.js @@ -40,16 +40,16 @@ 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 const finish = function() { @@ -115,23 +115,22 @@ upload.start = { 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 wait = false + let errorText = '' file.ready = true // Set status - if (xhr.status===200 && xhr.responseText==='1') { + if (xhr.status===200 && xhr.responseText==='true') { // Success - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') .status') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') .status') .html('Finished') .addClass('success') @@ -143,7 +142,7 @@ upload.start = { 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') @@ -153,7 +152,7 @@ upload.start = { 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') @@ -163,13 +162,13 @@ upload.start = { 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') } - $('.basicModal .rows .row:nth-child(' + (file.num+1) + ') p.notice') + $('.basicModal .rows .row:nth-child(' + (file.num + 1) + ') p.notice') .html(errorText) .show() @@ -210,11 +209,11 @@ upload.start = { // 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) @@ -236,7 +235,7 @@ upload.start = { files[i].ready = false files[i].supported = true - 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 @@ -387,7 +386,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() @@ -480,7 +479,7 @@ upload.start = { } // 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/_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..d50dc57 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 -------------------------------------------------------------- // 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']); }