Merge pull request #1 from qligier/develop

Develop
This commit is contained in:
Quentin Ligier 2016-03-16 20:00:06 +01:00
commit d0e35fb36f
98 changed files with 5095 additions and 5186 deletions

7
.gitignore vendored
View File

@ -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/
plugins/*
!plugins/Diagnostics/
!plugins/Log/

View File

@ -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**:<br>
Use tabs for indentation. No spaces.
Use tabs for indentation. Spaces for alignment.
- **Naming**:<br>
Keep variable and method names concise and descriptive.
- **Quotes**:<br>
Single-quoted strings are preferred to double-quoted strings
- **Comments**:<br>
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.

View File

@ -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 &#187;](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 &#187;](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 &#187;](https://github.com/cternes/Lychee-RSS) |
| lychee-FlashAir | Import from a Toshiba FlashAir WiFi SD card | [More &#187;](https://github.com/mhp/Lychee-FlashAir) |
| lychee-webroot | Controls photos accessibility and keeps Lychee files hidden | [More &#187;](https://github.com/Bramas/lychee-webroot) |
| lychee-create-medium | Generate missing medium size photos | [More &#187;](https://github.com/Bramas/lychee-create-medium) |
## Troubleshooting

BIN
dist/main.css vendored

Binary file not shown.

BIN
dist/main.js vendored Normal file → Executable file

Binary file not shown.

BIN
dist/view.js vendored

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
<?php
###
# @name ExamplePlugin
# @author Tobias Reich
# @copyright 2015 by Tobias Reich
###
namespace ExamplePlugin;
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
use SplObserver;
use SplSubject;
class ExamplePlugin implements SplObserver {
private $database = null;
private $settings = null;
public function __construct() {
public function __construct($database, $settings) {
# These params are passed to your plugin from Lychee
# Save them to access the database and settings of Lychee
$this->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

View File

@ -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]

View File

@ -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.

17
php/Access/Access.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace Lychee\Access;
use Lychee\Modules\Response;
abstract class Access {
final protected static function fnNotFound() {
Response::error('Function not found! Please check the spelling of the called function.');
}
}
?>

344
php/Access/Admin.php Normal file
View File

@ -0,0 +1,344 @@
<?php
namespace Lychee\Access;
use Lychee\Modules\Album;
use Lychee\Modules\Albums;
use Lychee\Modules\Import;
use Lychee\Modules\Photo;
use Lychee\Modules\Response;
use Lychee\Modules\Session;
use Lychee\Modules\Settings;
use Lychee\Modules\Validator;
final class Admin extends Access {
public static function init($fn) {
switch ($fn) {
// Albums functions
case 'Albums::get': self::getAlbumsAction(); break;
// Album functions
case 'Album::get': self::getAlbumAction(); break;
case 'Album::add': self::addAlbumAction(); break;
case 'Album::setTitle': self::setAlbumTitleAction(); break;
case 'Album::setDescription': self::setAlbumDescriptionAction(); break;
case 'Album::setPublic': self::setAlbumPublicAction(); break;
case 'Album::delete': self::deleteAlbumAction(); break;
case 'Album::merge': self::mergeAlbumsAction(); break;
// Photo functions
case 'Photo::get': self::getPhotoAction(); break;
case 'Photo::setTitle': self::setPhotoTitleAction(); break;
case 'Photo::setDescription': self::setPhotoDescriptionAction(); break;
case 'Photo::setStar': self::setPhotoStarAction(); break;
case 'Photo::setPublic': self::setPhotoPublicAction(); break;
case 'Photo::setAlbum': self::setPhotoAlbumAction(); break;
case 'Photo::setTags': self::setPhotoTagsAction(); break;
case 'Photo::duplicate': self::duplicatePhotoAction(); break;
case 'Photo::delete': self::deletePhotoAction(); break;
// Add functions
case 'Photo::add': self::uploadAction(); break;
case 'Import::url': self::importUrlAction(); break;
case 'Import::server': self::importServerAction(); break;
// Search functions
case 'search': self::searchAction(); break;
// Session functions
case 'Session::init': self::initAction(); break;
case 'Session::login': self::loginAction(); break;
case 'Session::logout': self::logoutAction(); break;
// Settings functions
case 'Settings::setLogin': self::setLoginAction(); break;
case 'Settings::setSorting': self::setSortingAction(); break;
case 'Settings::setDropboxKey': self::setDropboxKeyAction(); break;
// $_GET functions
case 'Album::getArchive': self::getAlbumArchiveAction(); break;
case 'Photo::getArchive': self::getPhotoArchiveAction(); break;
}
self::fnNotFound();
}
// Albums functions
private static function getAlbumsAction() {
$albums = new Albums();
Response::json($albums->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();
}
}
?>

185
php/Access/Guest.php Normal file
View File

@ -0,0 +1,185 @@
<?php
namespace Lychee\Access;
use Lychee\Modules\Album;
use Lychee\Modules\Albums;
use Lychee\Modules\Photo;
use Lychee\Modules\Response;
use Lychee\Modules\Session;
use Lychee\Modules\Validator;
final class Guest extends Access {
public static function init($fn) {
switch ($fn) {
// Albums functions
case 'Albums::get': self::getAlbumsAction(); break;
// Album functions
case 'Album::get': self::getAlbumAction(); break;
case 'Album::getPublic': self::checkAlbumAccessAction(); break;
// Photo functions
case 'Photo::get': self::getPhotoAction(); break;
// Session functions
case 'Session::init': self::initAction(); break;
case 'Session::login': self::loginAction(); break;
case 'Session::logout': self::logoutAction(); break;
// $_GET functions
case 'Album::getArchive': self::getAlbumArchiveAction(); break;
case 'Photo::getArchive': self::getPhotoArchiveAction(); break;
}
self::fnNotFound();
}
// Albums functions
private static function getAlbumsAction() {
$albums = new Albums();
Response::json($albums->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!');
}
}
}
?>

View File

@ -0,0 +1,45 @@
<?php
namespace Lychee\Access;
use Lychee\Modules\Config;
use Lychee\Modules\Response;
use Lychee\Modules\Validator;
final class Installation extends Access {
public static function init($fn) {
switch ($fn) {
case 'Config::create': self::configCreateAction(); break;
default: self::initAction(); break;
}
self::fnNotFound();
}
private static function configCreateAction() {
Validator::required(isset($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix']), __METHOD__);
echo Config::create($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['dbTablePrefix']);
}
private static function initAction() {
$return = array(
'status' => LYCHEE_STATUS_NOCONFIG
);
Response::json($return);
}
}
?>

647
php/Modules/Album.php Normal file
View File

@ -0,0 +1,647 @@
<?php
namespace Lychee\Modules;
use ZipArchive;
final class Album {
private $albumIDs = null;
/**
* @return boolean Returns true when successful.
*/
public function __construct($albumIDs) {
// Init vars
$this->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;
}
}
?>

191
php/Modules/Albums.php Normal file
View File

@ -0,0 +1,191 @@
<?php
namespace Lychee\Modules;
final class Albums {
/**
* @return boolean Returns true when successful.
*/
public function __construct() {
return true;
}
/**
* @return array|false Returns an array of albums or false on failure.
*/
public function get($public = true) {
// Call plugins
Plugins::get()->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;
}
}
?>

79
php/Modules/Config.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace Lychee\Modules;
final class Config {
/**
* Creates the configuration file.
* @return true|string Returns true when successful.
* Warning: Connection failed!
* Warning: Creation failed!
* Warning: Could not create file!
*/
public static function create($host, $user, $password, $name = 'lychee', $prefix = '') {
// Open a new connection to the MySQL server
$connection = Database::connect($host, $user, $password);
// Check if the connection was successful
if ($connection===false) return 'Warning: Connection failed!';
// Check if user can create the database before saving the configuration
if (Database::createDatabase($connection, $name)===false) return 'Warning: Creation failed!';
// Escape data
$host = mysqli_real_escape_string($connection, $host);
$user = mysqli_real_escape_string($connection, $user);
$password = mysqli_real_escape_string($connection, $password);
$name = mysqli_real_escape_string($connection, $name);
$prefix = mysqli_real_escape_string($connection, $prefix);
// Save config.php
$config = "<?php
// Database configuration
\$dbHost = '$host'; // Host of the database
\$dbUser = '$user'; // Username of the database
\$dbPassword = '$password'; // Password of the database
\$dbName = '$name'; // Database name
\$dbTablePrefix = '$prefix'; // Table prefix
?>";
// 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
));
}
}
?>

411
php/Modules/Database.php Executable file
View File

@ -0,0 +1,411 @@
<?php
namespace Lychee\Modules;
use Mysqli;
final class Database {
private $connection = null;
private static $instance = null;
private static $versions = array(
'020700', // 2.7.0
'030000', // 3.0.0
'030001', // 3.0.1
'030003', // 3.0.3
'030100' // 3.1.0
);
/**
* @return object Returns a new or cached connection.
*/
public static function get() {
if (!self::$instance) {
$credentials = Config::get();
self::$instance = new self(
$credentials['host'],
$credentials['user'],
$credentials['password'],
$credentials['name'],
$credentials['prefix']
);
}
return self::$instance->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;
}
}
?>

195
php/Modules/Import.php Normal file
View File

@ -0,0 +1,195 @@
<?php
namespace Lychee\Modules;
final class Import {
/**
* Creates an array similar to a file upload array and adds the photo to Lychee.
* @return boolean Returns true when photo import was successful.
*/
private function photo($path, $albumID = 0) {
// 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(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;
}
}
?>

47
php/Modules/Log.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace Lychee\Modules;
final class Log {
/**
* @return boolean Returns true when successful.
*/
public static function notice($connection, $function, $line, $text = '') {
return Log::text($connection, 'notice', $function, $line, $text);
}
/**
* @return boolean Returns true when successful.
*/
public static function error($connection, $function, $line, $text = '') {
return Log::text($connection, 'error', $function, $line, $text);
}
/**
* @return boolean Returns true when successful.
*/
private static function text($connection, $type, $function, $line, $text = '') {
// Check dependencies
Validator::required(isset($connection, $type, $function, $line, $text), __METHOD__);
// Get time
$sysstamp = time();
// Save in database
$query = Database::prepare($connection, "INSERT INTO ? (time, type, function, line, text) VALUES ('?', '?', '?', '?', '?')", array(LYCHEE_TABLE_LOG, $sysstamp, $type, $function, $line, $text));
$result = Database::execute($connection, $query, null, null);
if ($result===false) return false;
return true;
}
}
?>

1243
php/Modules/Photo.php Executable file

File diff suppressed because it is too large Load Diff

99
php/Modules/Plugins.php Normal file
View File

@ -0,0 +1,99 @@
<?php
namespace Lychee\Modules;
use SplSubject;
use SplObserver;
final class Plugins implements SplSubject {
private static $instance = null;
private $observers = array();
public $action = null;
public $args = null;
public static function get() {
if (!self::$instance) {
$files = Settings::get()['plugins'];
self::$instance = new self($files);
}
return self::$instance;
}
private function __construct(array $plugins) {
// Load plugins
foreach ($plugins as $plugin) {
if ($plugin==='') continue;
$this->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;
}
}
?>

25
php/Modules/Response.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace Lychee\Modules;
final class Response {
public static function warning($msg) {
exit('Warning: ' . $msg);
}
public static function error($msg) {
exit('Error: ' . $msg);
}
public static function json($str) {
exit(json_encode($str));
}
}

133
php/Modules/Session.php Executable file
View File

@ -0,0 +1,133 @@
<?php
namespace Lychee\Modules;
final class Session {
/**
* Reads and returns information about the Lychee installation.
* @return array Returns an array with the login status and configuration.
*/
public function init($public = true) {
// Call plugins
Plugins::get()->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;
}
}
?>

226
php/Modules/Settings.php Executable file
View File

@ -0,0 +1,226 @@
<?php
namespace Lychee\Modules;
final class Settings {
private static $cache = null;
/**
* @return array Returns the settings of Lychee.
*/
public static function get() {
if (self::$cache) return self::$cache;
// Execute query
$query = Database::prepare(Database::get(), "SELECT * FROM ?", array(LYCHEE_TABLE_SETTINGS));
$settings = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
// Add each to return
while ($setting = $settings->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;
}
}
?>

41
php/Modules/Validator.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace Lychee\Modules;
final class Validator {
public static function required($available = false, $function) {
if ($available===false) Response::error('Missing parameters. Can not execute function ' . $function);
return true;
}
public static function isAlbumIDs($albumIDs) {
return (preg_match('/^[0-9\,]{1,}$/', $albumIDs)===1 ? true : false);
}
public static function isAlbumID($albumID) {
return (preg_match('/^[0-9sfr]{1,}$/', $albumID)===1 ? true : false);
}
public static function isPhotoIDs($photoIDs) {
return (preg_match('/^[0-9\,]{1,}$/', $photoIDs)===1 ? true : false);
}
public static function isPhotoID($photoID) {
return (preg_match('/^[0-9]{14}$/', $photoID)===1 ? true : false);
}
}
?>

View File

@ -1,29 +0,0 @@
<?php
###
# @name Access
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Access {
protected $database = null;
protected $plugins = null;
protected $settings = null;
public function __construct($database, $plugins, $settings) {
# Init vars
$this->database = $database;
$this->plugins = $plugins;
$this->settings = $settings;
return true;
}
}
?>

View File

@ -1,321 +0,0 @@
<?php
###
# @name Admin Access
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_ADMIN')) exit('Error: You are not allowed to access this area!');
class Admin extends Access {
public function check($fn) {
switch ($fn) {
# Album functions
case 'Album::getAll': $this->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();
}
}

View File

@ -1,180 +0,0 @@
<?php
###
# @name Guest Access (Public Mode)
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_GUEST')) exit('Error: You are not allowed to access this area!');
class Guest extends Access {
public function check($fn) {
switch ($fn) {
# Album functions
case 'Album::getAll': $this->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!');
}
}
}
?>

View File

@ -1,47 +0,0 @@
<?php
###
# @name Installation Access
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_INSTALLATION')) exit('Error: You are not allowed to access this area!');
class Installation extends Access {
public function check($fn) {
switch ($fn) {
case 'Database::createConfig': $this->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);
}
}
?>

View File

@ -1,93 +0,0 @@
<?php
###
# @name API
# @copyright 2015 by Tobias Reich
###
if (!empty($_POST['function'])||!empty($_GET['function'])) {
session_start();
date_default_timezone_set('UTC');
# Load required files
require(__DIR__ . '/define.php');
require(__DIR__ . '/autoload.php');
require(__DIR__ . '/modules/misc.php');
if (file_exists(LYCHEE_CONFIG_FILE)) require(LYCHEE_CONFIG_FILE);
else {
###
# Installation Access
# Limited access to configure Lychee. Only available when the config.php file is missing.
###
define('LYCHEE_ACCESS_INSTALLATION', true);
$installation = new Installation(null, null, null);
$installation->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!');
}
?>

View File

@ -1,33 +1,24 @@
<?php
###
# @name Autoload
# @copyright 2015 by Tobias Reich
###
spl_autoload_register(function($class) {
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
$classPath = str_replace('Lychee\\', '', $class);
$classPath = str_replace('\\', DIRECTORY_SEPARATOR, $classPath);
function lycheeAutoloaderModules($class_name) {
$file = LYCHEE . 'php/' . $classPath . '.php';
$modules = array('Album', 'Database', 'Import', 'Log', 'Module', 'Photo', 'Plugins', 'Session', 'Settings');
if (!in_array($class_name, $modules)) return false;
if (file_exists($file)===true) require $file;
$file = LYCHEE . 'php/modules/' . $class_name . '.php';
if (file_exists($file)!==false) require $file;
});
}
spl_autoload_register(function($class) {
function lycheeAutoloaderAccess($class_name) {
$classPath = str_replace('\\', DIRECTORY_SEPARATOR, $class);
$access = array('Access', 'Admin', 'Guest', 'Installation');
if (!in_array($class_name, $access)) return false;
$file = LYCHEE . 'plugins/' . $classPath . '.php';
$file = LYCHEE . 'php/access/' . $class_name . '.php';
if (file_exists($file)!==false) require $file;
if (file_exists($file)===true) require $file;
}
spl_autoload_register('lycheeAutoloaderModules');
spl_autoload_register('lycheeAutoloaderAccess');
});
?>

View File

@ -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,

View File

@ -1,5 +1,4 @@
# Dump of table lychee_log
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `?` (

View File

@ -1,5 +1,4 @@
# Dump of table lychee_photos
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `?` (

View File

@ -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',''),

View File

@ -1,5 +1,4 @@
# Dump of table lychee_settings
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `?` (

View File

@ -1,44 +0,0 @@
<?php
###
# @name Update to version 2.1
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
$query = Database::prepare($database, "SELECT `tags` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS));
if(!$database->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;
}
?>

View File

@ -1,20 +0,0 @@
<?php
###
# @name Update to version 2.1.1
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
$query = Database::prepare($database, "ALTER TABLE `?` CHANGE `value` `value` VARCHAR( 200 ) NULL DEFAULT ''", array(LYCHEE_TABLE_SETTINGS));
$result = $database->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;
?>

View File

@ -1,23 +0,0 @@
<?php
###
# @name Update to version 2.2
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
$query = Database::prepare($database, "SELECT `visible` FROM `?` LIMIT 1", array(LYCHEE_TABLE_ALBUMS));
if (!$database->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;
?>

View File

@ -1,156 +0,0 @@
<?php
###
# @name Update to version 2.5
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
# Add `plugins`
$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'plugins' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
$result = $database->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;
?>

View File

@ -1,24 +0,0 @@
<?php
###
# @name Update to version 2.5.5
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
# Add `checksum`
$query = Database::prepare($database, "SELECT `checksum` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS));
if (!$database->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;
?>

View File

@ -1,24 +0,0 @@
<?php
###
# @name Update to version 2.6.1
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
# Add `downloadable`
$query = Database::prepare($database, "SELECT `downloadable` FROM `?` LIMIT 1", array(LYCHEE_TABLE_ALBUMS));
if (!$database->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;
?>

View File

@ -1,47 +0,0 @@
<?php
###
# @name Update to version 2.6.2
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
# Add a checksum
$query = Database::prepare($database, "SELECT `id`, `url` FROM `?` WHERE `checksum` IS NULL", array(LYCHEE_TABLE_PHOTOS));
$result = $database->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;
?>

View File

@ -1,43 +1,37 @@
<?php
###
# @name Update to version 2.7.0
# @copyright 2015 by Tobias Reich
###
/**
* Update to version 2.7.0
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
use Lychee\Modules\Database;
use Lychee\Modules\Response;
// Add medium to photos
$query = Database::prepare($connection, "SELECT `medium` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS));
$result = Database::execute($connection, $query, 'update_020700', __LINE__);
if ($result===false) {
$query = Database::prepare($connection, "ALTER TABLE `?` ADD `medium` TINYINT(1) NOT NULL DEFAULT 0", array(LYCHEE_TABLE_PHOTOS));
$result = Database::execute($connection, $query, 'update_020700', __LINE__);
if ($result===false) Response::error('Could not add medium-field to database!');
# Add medium to photos
$query = Database::prepare($database, "SELECT `medium` FROM `?` LIMIT 1", array(LYCHEE_TABLE_PHOTOS));
if (!$database->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!');
?>

View File

@ -1,37 +1,32 @@
<?php
###
# @name Update to version 3.0.0
# @copyright 2015 by Tobias Reich
###
/**
* Update to version 3.0.0
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
use Lychee\Modules\Database;
use Lychee\Modules\Response;
# Remove login
# Login now saved as crypt without md5. Legacy code has been removed.
$query = Database::prepare($database, "UPDATE `?` SET `value` = '' WHERE `key` = 'username' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
$resetUsername = $database->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!');
?>

View File

@ -1,62 +1,62 @@
<?php
###
# @name Update to version 3.0.1
# @copyright 2015 by Tobias Reich
###
/**
* Update to version 3.0.1
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
use Lychee\Modules\Database;
use Lychee\Modules\Response;
# Change length of photo title
$query = Database::prepare($database, "ALTER TABLE `?` CHANGE `title` `title` VARCHAR( 100 ) NOT NULL DEFAULT ''", array(LYCHEE_TABLE_PHOTOS));
$result = $database->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!');
?>

View File

@ -1,25 +1,28 @@
<?php
###
# @name Update to version 3.0.3
# @copyright 2015 by Tobias Reich
###
/**
* Update to version 3.0.3
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
use Lychee\Modules\Database;
use Lychee\Modules\Response;
// Add skipDuplicates to settings
$query = Database::prepare($connection, "SELECT `key` FROM `?` WHERE `key` = 'skipDuplicates' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
$result = Database::execute($connection, $query, 'update_030003', __LINE__);
if ($result===false) Response::error('Could not get current skipDuplicates from database!');
# Add skipDuplicates to settings
$query = Database::prepare($database, "SELECT `key` FROM `?` WHERE `key` = 'skipDuplicates' LIMIT 1", array(LYCHEE_TABLE_SETTINGS));
$result = $database->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!');
?>

View File

@ -0,0 +1,19 @@
<?php
/**
* Update to version 3.1.0
*/
use Lychee\Modules\Database;
use Lychee\Modules\Response;
// Change length of album id field
$query = Database::prepare($connection, "ALTER TABLE `?` CHANGE `id` `id` BIGINT(14) NOT NULL", array(LYCHEE_TABLE_ALBUMS));
$result = Database::execute($connection, $query, 'update_030100', __LINE__);
if ($result===false) Response::error('Could not adjust the length of the album id field!');
// Set version
// if (Database::setVersion($connection, '030100')===false) Response::error('Could not update version of database!');
?>

View File

@ -1,19 +1,14 @@
<?php
###
# @name Define
# @copyright 2015 by Tobias Reich
###
# Define root
// Define root
define('LYCHEE', substr(__DIR__, 0, -3));
# Define status
// Define status
define('LYCHEE_STATUS_NOCONFIG', 0);
define('LYCHEE_STATUS_LOGGEDOUT', 1);
define('LYCHEE_STATUS_LOGGEDIN', 2);
# Define dirs
// Define dirs
define('LYCHEE_DATA', LYCHEE . 'data/');
define('LYCHEE_SRC', LYCHEE . 'src/');
define('LYCHEE_UPLOADS', LYCHEE . 'uploads/');
@ -23,26 +18,25 @@ define('LYCHEE_UPLOADS_THUMB', LYCHEE_UPLOADS . 'thumb/');
define('LYCHEE_UPLOADS_IMPORT', LYCHEE_UPLOADS . 'import/');
define('LYCHEE_PLUGINS', LYCHEE . 'plugins/');
# Define files
// Define files
define('LYCHEE_CONFIG_FILE', LYCHEE_DATA . 'config.php');
# Define urls
// Define urls
define('LYCHEE_URL_UPLOADS_BIG', 'uploads/big/');
define('LYCHEE_URL_UPLOADS_MEDIUM', 'uploads/medium/');
define('LYCHEE_URL_UPLOADS_THUMB', 'uploads/thumb/');
function defineTablePrefix($dbTablePrefix) {
function defineTablePrefix($dbTablePrefix = '') {
# This part is wrapped into a function, because it needs to be called
# after the config-file has been loaded. Other defines are also available
# before the config-file has been loaded.
// This part is wrapped into a function, because it needs to be called
// after the config-file has been loaded. Other defines are available
// before the config-file has been loaded.
# Parse table prefix
# Old users do not have the table prefix stored in their config-file
if (!isset($dbTablePrefix)||$dbTablePrefix==='') $dbTablePrefix = '';
else $dbTablePrefix .= '_';
// Parse table prefix
// Old users do not have the table prefix stored in their config-file
if ($dbTablePrefix!=='') $dbTablePrefix .= '_';
# Define tables
// Define tables
define('LYCHEE_TABLE_ALBUMS', $dbTablePrefix . 'lychee_albums');
define('LYCHEE_TABLE_LOG', $dbTablePrefix . 'lychee_log');
define('LYCHEE_TABLE_PHOTOS', $dbTablePrefix . 'lychee_photos');

View File

@ -0,0 +1,35 @@
<?php
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;
}
?>

View File

@ -0,0 +1,16 @@
<?php
function generateID() {
// Generate id based on the current microtime
$id = str_replace('.', '', microtime(true));
// Ensure that the id has a length of 14 chars
while(strlen($id)<14) $id .= 0;
// Return the integer value of the id
return intval($id);
}
?>

View File

@ -0,0 +1,13 @@
<?php
function getExtension($filename) {
$extension = strpos($filename, '.') !== false
? strrchr($filename, '.')
: '';
return $extension;
}
?>

View File

@ -0,0 +1,56 @@
<?php
use Lychee\Modules\Database;
use Lychee\Modules\Photo;
function getGraphHeader($photoID) {
$photo = new Photo($photoID);
if ($photo->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 = '<!-- General Meta Data -->';
$return .= '<meta name="title" content="' . $row->title . '">';
$return .= '<meta name="description" content="' . $row->description . ' - via Lychee">';
$return .= '<link rel="image_src" type="image/jpeg" href="' . $picture . '">';
$return .= '<!-- Twitter Meta Data -->';
$return .= '<meta name="twitter:card" content="photo">';
$return .= '<meta name="twitter:title" content="' . $row->title . '">';
$return .= '<meta name="twitter:image:src" content="' . $picture . '">';
$return .= '<!-- Facebook Meta Data -->';
$return .= '<meta property="og:title" content="' . $row->title . '">';
$return .= '<meta property="og:description" content="' . $row->description . ' - via Lychee">';
$return .= '<meta property="og:image" content="' . $picture . '">';
$return .= '<meta property="og:url" content="' . $url . '">';
return $return;
}
?>

View File

@ -0,0 +1,38 @@
<?php
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);
}
?>

View File

@ -0,0 +1,13 @@
<?php
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;
}
?>

75
php/helpers/search.php Executable file
View File

@ -0,0 +1,75 @@
<?php
use Lychee\Modules\Album;
use Lychee\Modules\Database;
use Lychee\Modules\Photo;
use Lychee\Modules\Settings;
/**
* @return array|false Returns an array with albums and photos.
*/
function search($term) {
// Initialize return var
$return = array(
'photos' => 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;
}
?>

91
php/index.php Executable file
View File

@ -0,0 +1,91 @@
<?php
/**
* @author Tobias Reich
* @copyright 2016 by Tobias Reich
*/
namespace Lychee;
use Lychee\Modules\Config;
use Lychee\Modules\Response;
use Lychee\Modules\Settings;
use Lychee\Modules\Validator;
use Lychee\Access\Installation;
use Lychee\Access\Admin;
use Lychee\Access\Guest;
require(__DIR__ . '/define.php');
require(__DIR__ . '/autoload.php');
require(__DIR__ . '/helpers/fastImageCopyResampled.php');
require(__DIR__ . '/helpers/generateID.php');
require(__DIR__ . '/helpers/getExtension.php');
require(__DIR__ . '/helpers/getGraphHeader.php');
require(__DIR__ . '/helpers/getHashedString.php');
require(__DIR__ . '/helpers/hasPermissions.php');
require(__DIR__ . '/helpers/search.php');
// Define the called function
if (isset($_POST['function'])) $fn = $_POST['function'];
else if (isset($_GET['function'])) $fn = $_GET['function'];
else $fn = null;
// Check if a function has been specified
if (!empty($fn)) {
// Start the session and set the default timezone
session_start();
date_default_timezone_set('UTC');
// Validate parameters
if (isset($_POST['albumIDs'])&&Validator::isAlbumIDs($_POST['albumIDs'])===false) Response::error('Wrong parameter type for albumIDs!');
if (isset($_POST['photoIDs'])&&Validator::isPhotoIDs($_POST['photoIDs'])===false) Response::error('Wrong parameter type for photoIDs!');
if (isset($_POST['albumID'])&&Validator::isAlbumID($_POST['albumID'])==false) Response::error('Wrong parameter type for albumID!');
if (isset($_POST['photoID'])&&Validator::isPhotoID($_POST['photoID'])==false) Response::error('Wrong parameter type for photoID!');
// Check if a configuration exists
if (Config::exists()===false) {
/**
* Installation Access
* Limited access to configure Lychee. Only available when the config.php file is missing.
*/
Installation::init($fn);
exit();
}
// Check if user is logged
if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&&
(isset($_SESSION['identifier'])&&$_SESSION['identifier']===Settings::get()['identifier'])) {
/**
* Admin Access
* Full access to Lychee. Only with correct password/session.
*/
Admin::init($fn);
exit();
} else {
/**
* Guest Access
* Access to view all public folders and photos in Lychee.
*/
Guest::init($fn);
exit();
}
} else {
Response::error('No API function specified!');
}
?>

View File

@ -1,759 +0,0 @@
<?php
###
# @name Album Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Album extends Module {
private $database = null;
private $settings = null;
private $albumIDs = null;
public function __construct($database, $plugins, $settings, $albumIDs) {
# Init vars
$this->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;
}
}
?>

View File

@ -1,338 +0,0 @@
<?php
###
# @name Database Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Database extends Module {
static function connect($host = 'localhost', $user, $password, $name = 'lychee') {
# Check dependencies
Module::dependencies(isset($host, $user, $password, $name));
$database = new mysqli($host, $user, $password);
# Check connection
if ($database->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 = "<?php
###
# @name Configuration
# @author Tobias Reich
# @copyright 2015 Tobias Reich
###
if(!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
# Database configuration
\$dbHost = '$host'; # Host of the database
\$dbUser = '$user'; # Username of the database
\$dbPassword = '$password'; # Password of the database
\$dbName = '$name'; # Database name
\$dbTablePrefix = '$prefix'; # Table prefix
?>";
# 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;
}
}
?>

View File

@ -1,211 +0,0 @@
<?php
###
# @name Upload Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Import extends Module {
private $database = null;
private $settings = null;
public function __construct($database, $plugins, $settings) {
# Init vars
$this->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;
}
}
?>

View File

@ -1,49 +0,0 @@
<?php
###
# @name Log Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Log extends Module {
public static function notice($database, $function, $line, $text = '') {
return Log::text($database, 'notice', $function, $line, $text);
}
public static function warning($database, $function, $line, $text = '') {
return Log::text($database, 'warning', $function, $line, $text);
}
public static function error($database, $function, $line, $text = '') {
return Log::text($database, 'error', $function, $line, $text);
}
public static function text($database, $type, $function, $line, $text = '') {
# Check dependencies
Module::dependencies(isset($database, $type, $function, $line, $text));
# Get time
$sysstamp = time();
# Save in database
$query = Database::prepare($database, "INSERT INTO ? (time, type, function, line, text) VALUES ('?', '?', '?', '?', '?')", array(LYCHEE_TABLE_LOG, $sysstamp, $type, $function, $line, $text));
$result = $database->query($query);
if (!$result) return false;
return true;
}
}
?>

View File

@ -1,36 +0,0 @@
<?php
###
# @name Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Module {
protected $plugins = null;
protected function plugins($name, $location, $args) {
if (!isset($this->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.');
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +0,0 @@
<?php
###
# @name Plugins Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Plugins implements \SplSubject {
private $files = array();
private $observers = array();
public $action = null;
public $args = null;
public function __construct($files, $database, $settings) {
if (!isset($files)) return false;
# Init vars
$this->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;
}
}
?>

View File

@ -1,156 +0,0 @@
<?php
###
# @name Session Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Session extends Module {
private $settings = null;
public function __construct($plugins, $settings) {
# Init vars
$this->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;
}
}
?>

View File

@ -1,255 +0,0 @@
<?php
###
# @name Settings Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Settings extends Module {
private $database = null;
public function __construct($database) {
# Init vars
$this->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;
}
}
?>

View File

@ -1,199 +0,0 @@
<?php
###
# @name Misc Module
# @copyright 2015 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function search($database, $settings, $term) {
if (!isset($database, $settings, $term)) return false;
$return['albums'] = '';
# Initialize return var
$return = array(
'photos' => 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 = '<!-- General Meta Data -->';
$return .= '<meta name="title" content="' . $row->title . '">';
$return .= '<meta name="description" content="' . $row->description . ' - via Lychee">';
$return .= '<link rel="image_src" type="image/jpeg" href="' . $picture . '">';
$return .= '<!-- Twitter Meta Data -->';
$return .= '<meta name="twitter:card" content="photo">';
$return .= '<meta name="twitter:title" content="' . $row->title . '">';
$return .= '<meta name="twitter:image:src" content="' . $picture . '">';
$return .= '<!-- Facebook Meta Data -->';
$return .= '<meta property="og:title" content="' . $row->title . '">';
$return .= '<meta property="og:description" content="' . $row->description . ' - via Lychee">';
$return .= '<meta property="og:image" content="' . $picture . '">';
$return .= '<meta property="og:url" content="' . $url . '">';
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;
}
?>

View File

@ -0,0 +1,141 @@
<?php
/**
* @author Tobias Reich
* @copyright 2015 by Tobias Reich
* @description This file takes a look at your Lychee-configuration and displays all errors it can find.
*/
namespace Diagnostics;
use Mysqli;
use Imagick;
use Lychee\Modules\Settings;
$lychee = __DIR__ . '/../../';
require($lychee . 'php/define.php');
require($lychee . 'php/autoload.php');
require($lychee . 'php/helpers/hasPermissions.php');
// Start the session
session_start();
// Set content
header('content-type: text/plain');
// Declare
$error = '';
// Show separator
echo('Diagnostics' . PHP_EOL);
echo('-----------' . PHP_EOL);
// PHP Version
if (floatval(phpversion())<5.5) $error .= ('Error: Upgrade to PHP 5.5 or higher' . PHP_EOL);
// Extensions
if (!extension_loaded('session')) $error .= ('Error: PHP session extension not activated' . PHP_EOL);
if (!extension_loaded('exif')) $error .= ('Error: PHP exif extension not activated' . PHP_EOL);
if (!extension_loaded('mbstring')) $error .= ('Error: PHP mbstring extension not activated' . PHP_EOL);
if (!extension_loaded('gd')) $error .= ('Error: PHP gd extension not activated' . PHP_EOL);
if (!extension_loaded('mysqli')) $error .= ('Error: PHP mysqli extension not activated' . PHP_EOL);
if (!extension_loaded('json')) $error .= ('Error: PHP json extension not activated' . PHP_EOL);
if (!extension_loaded('zip')) $error .= ('Error: PHP zip extension not activated' . PHP_EOL);
// Permissions
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false) $error .= ('Error: \'uploads/big\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_MEDIUM)===false) $error .= ('Error: \'uploads/medium\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_THUMB)===false) $error .= ('Error: \'uploads/thumb\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_IMPORT)===false) $error .= ('Error: \'uploads/import\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS)===false) $error .= ('Error: \'uploads/\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_DATA)===false) $error .= ('Error: \'data/\' is missing or has insufficient read/write privileges' . 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);
// Load config
if (!file_exists(LYCHEE_CONFIG_FILE)) exit('Error: Configuration not found. Please install Lychee for additional tests');
require(LYCHEE_CONFIG_FILE);
// Database
$database = new Mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if (mysqli_connect_errno()!=0) $error .= ('Error: ' . mysqli_connect_errno() . ': ' . mysqli_connect_error() . '' . PHP_EOL);
// 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);
// Load settings
$settings = Settings::get();
// 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['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);
// 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.');
}
?>

64
plugins/Log/index.php Normal file
View File

@ -0,0 +1,64 @@
<?php
/**
* @author Tobias Reich
* @copyright 2015 by Tobias Reich
* @description This file queries the database for log messages and displays them if present.
*/
namespace Log;
use Mysqli;
use Lychee\Modules\Database;
use Lychee\Modules\Settings;
$lychee = __DIR__ . '/../../';
require($lychee . 'php/define.php');
require($lychee . 'php/autoload.php');
// Start the session
session_start();
// Set content
header('content-type: text/plain');
// Load config
if (!file_exists(LYCHEE_CONFIG_FILE)) exit('Error 001: Configuration not found. Please install Lychee first.');
require(LYCHEE_CONFIG_FILE);
// Ensure that user is logged in
if ((isset($_SESSION['login'])&&$_SESSION['login']===true)&&
(isset($_SESSION['identifier'])&&$_SESSION['identifier']===Settings::get()['identifier'])) {
// Result
$query = Database::prepare(Database::get(), "SELECT FROM_UNIXTIME(time), type, function, line, text FROM ?", array(LYCHEE_TABLE_LOG));
$result = Database::get()->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.');
}
?>

View File

@ -1,143 +0,0 @@
<?php
###
# @name Check Plugin
# @author Tobias Reich
# @copyright 2015 by Tobias Reich
# @description This file takes a look at your Lychee-configuration and displays all errors it can find.
###
# Location
$lychee = __DIR__ . '/../../';
# Load requirements
require($lychee . 'php/define.php');
require($lychee . 'php/autoload.php');
require($lychee . 'php/modules/misc.php');
# Set content
header('content-type: text/plain');
# Declare
$error = '';
# Show separator
echo('Diagnostics' . PHP_EOL);
echo('-----------' . PHP_EOL);
# PHP Version
if (floatval(phpversion())<5.5) $error .= ('Error: Upgrade to PHP 5.5 or higher' . PHP_EOL);
# Extensions
if (!extension_loaded('session')) $error .= ('Error: PHP session extension not activated' . PHP_EOL);
if (!extension_loaded('exif')) $error .= ('Error: PHP exif extension not activated' . PHP_EOL);
if (!extension_loaded('mbstring')) $error .= ('Error: PHP mbstring extension not activated' . PHP_EOL);
if (!extension_loaded('gd')) $error .= ('Error: PHP gd extension not activated' . PHP_EOL);
if (!extension_loaded('mysqli')) $error .= ('Error: PHP mysqli extension not activated' . PHP_EOL);
if (!extension_loaded('json')) $error .= ('Error: PHP json extension not activated' . PHP_EOL);
if (!extension_loaded('zip')) $error .= ('Error: PHP zip extension not activated' . PHP_EOL);
# Permissions
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false) $error .= ('Error: \'uploads/big\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_MEDIUM)===false) $error .= ('Error: \'uploads/medium\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_THUMB)===false) $error .= ('Error: \'uploads/thumb\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_IMPORT)===false) $error .= ('Error: \'uploads/import\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS)===false) $error .= ('Error: \'uploads/\' is missing or has insufficient read/write privileges' . PHP_EOL);
if (hasPermissions(LYCHEE_DATA)===false) $error .= ('Error: \'data/\' is missing or has insufficient read/write privileges' . PHP_EOL);
# Load config
if (!file_exists(LYCHEE_CONFIG_FILE)) exit('Error: Configuration not found. Please install Lychee for additional tests');
else require(LYCHEE_CONFIG_FILE);
# Define the table prefix
if (!isset($dbTablePrefix)) $dbTablePrefix = '';
defineTablePrefix($dbTablePrefix);
# Database
$database = new mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if (mysqli_connect_errno()!=0) $error .= ('Error: ' . mysqli_connect_errno() . ': ' . mysqli_connect_error() . '' . PHP_EOL);
# Load settings
$settings = new Settings($database);
$settings = $settings->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();
}
?>

View File

@ -1,81 +0,0 @@
<?php
###
# @name Display Log Plugin
# @author Tobias Reich
# @copyright 2015 by Tobias Reich
# @description This file queries the database for log messages and displays them if present.
###
# Location
$lychee = __DIR__ . '/../../';
# Load requirements
require($lychee . 'php/define.php');
require($lychee . 'php/autoload.php');
require($lychee . 'php/modules/misc.php');
# Set content
header('content-type: text/plain');
# Load config
if (!file_exists(LYCHEE_CONFIG_FILE)) exit('Error 001: Configuration not found. Please install Lychee first.');
require(LYCHEE_CONFIG_FILE);
# Define the table prefix
if (!isset($dbTablePrefix)) $dbTablePrefix = '';
defineTablePrefix($dbTablePrefix);
# Declare
$result = '';
# Database
$database = new mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if (mysqli_connect_errno()!=0) {
echo 'Error 100: ' . mysqli_connect_errno() . ': ' . mysqli_connect_error() . '' . PHP_EOL;
exit();
}
# Load settings
$settings = new Settings($database);
$settings = $settings->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();
}
?>

View File

@ -1,6 +1,6 @@
{
"name": "Lychee",
"version": "3.0.9",
"version": "3.1.0",
"description": "Self-hosted photo-management done right.",
"authors": "Tobias Reich <tobias@electerious.com>",
"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"
}
}

View File

@ -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]

View File

@ -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`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!</p>`
} 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, '&apos;')
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) {
<form>
<div class='choice'>
<label>
<input type='checkbox' name='visible'>
<input type='checkbox' name='hidden'>
<span class='checkbox'>${ build.iconic('check') }</span>
<span class='label'>Visible</span>
<span class='label'>Hidden</span>
</label>
<p>Listed to visitors of your Lychee.</p>
<p>Only people with the direct link can view this album.</p>
</div>
<div class='choice'>
<label>
@ -413,7 +411,7 @@ album.setPublic = function(albumID, modal, e) {
<span class='checkbox'>${ build.iconic('check') }</span>
<span class='label'>Password protected</span>
</label>
<p>Only accessible with a valid password.</p>
<p>Album only accessible with a valid password.</p>
<input class='text' name='passwordtext' type='password' placeholder='password' value=''>
</div>
</form>
@ -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, '&apos;')
// 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, '&apos;')
// Fallback for second album without a title
if (sTitle==='') sTitle = 'Untitled'
msg = lychee.html`<p>Are you sure you want to merge the album '$${ sTitle }' into the album '$${ title }'?</p>`
@ -596,7 +593,7 @@ album.merge = function(albumIDs) {
lychee.error(null, params, data)
} else {
albums.refresh()
albums.load()
lychee.goto()
}
})

View File

@ -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) {

View File

@ -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)

View File

@ -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`<div id='image' class='$${ visibleControls===true ? '' : 'full' }'><div><img src='$${ data.url }' draggable='false'></div></div>`
html += lychee.html`<img id='image' class='$${ visibleControls===true ? '' : 'full' }' src='$${ data.url }' draggable='false'>`
} else {
html += lychee.html`<div id='image' class='$${ visibleControls===true ? '' : 'full' }'><div><img src='$${ data.url }' srcset='$${ data.medium } 1920w, $${ data.url } $${ data.width }w' draggable='false'></div></div>`
html += lychee.html`<img id='image' class='$${ visibleControls===true ? '' : 'full' }' src='$${ data.url }' srcset='$${ data.medium } 1920w, $${ data.url } $${ data.width }w' draggable='false'>`
}
@ -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`
<div class='row'>

View File

@ -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`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
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`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
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`<img class='cover' width='16' height='16' src='$${ this.thumbUrl }'><div class='title'>$${ this.title }</div>`
if (this.title==='') this.title = 'Untitled'
if (this.id!=photoID) items.push({ title, fn: () => lychee.goto(albumID + '/' + this.id) })
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbUrl }'><div class='title'>$${ this.title }</div>`
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`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
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: `<input readonly id="link" value="${ link }">`, fn: () => {}, class: 'basicContext__item--noHover' },

View File

@ -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)

View File

@ -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
})

View File

@ -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)

View File

@ -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 dont 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

View File

@ -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')
}
}
}

View File

@ -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()
}

View File

@ -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(`<link data-prefetch rel="prefetch" href="${ url }">`)
@ -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: "<p>This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.</p>",
body: '<p>This photo is located in a public album. To make this photo private or public, edit the visibility of the associated album.</p>',
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

View File

@ -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()
}

View File

@ -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() {

View File

@ -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')

View File

@ -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() {

View File

@ -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()

View File

@ -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 dont 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()

View File

@ -65,18 +65,6 @@
}
}
// PopIn -------------------------------------------------------------- //
@keyframes popIn {
0% {
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1);
}
}
// Pulse -------------------------------------------------------------- //
@keyframes pulse {
0% {

View File

@ -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 -------------------------------------------------------------- //

View File

@ -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 -------------------------------------------------------------- //

View File

@ -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']);
}