2014-04-04 19:59:39 +00:00
< ? php
2016-01-26 14:31:53 +00:00
namespace Lychee\Modules ;
2014-04-04 19:59:39 +00:00
2016-01-26 14:31:53 +00:00
use Mysqli ;
2014-04-04 19:59:39 +00:00
2016-01-30 20:33:31 +00:00
final class Database {
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
private $connection = null ;
private static $instance = null ;
2016-01-19 10:06:54 +00:00
private static $versions = array (
2016-01-30 20:43:57 +00:00
'020700' , // 2.7.0
'030000' , // 3.0.0
'030001' , // 3.0.1
2016-03-12 22:51:33 +00:00
'030003' , // 3.0.3
2016-05-02 17:51:21 +00:00
'030100' , // 3.1.0
Added basic subalbum support.
That is, albums can now contain other albums, which are shown at
the top of the album view. This required some changes to album.js
and the contextMenu.js, because this view contains now both
photos and albums.
The contextMenu on this view has been kept simple by requiring
the user to select either only albums or only photos, but not
a mixture of both.
This feature required a database change, so that the version
has been updated to 3.1.3.
At the moment, album and photo operations (make public, download,
delete, merge) are still "flat", i.e. don't respect the album
hierarchy.
2016-07-28 14:01:36 +00:00
'030102' , // 3.1.2
'030103' // 3.1.3
2016-01-19 10:06:54 +00:00
);
2016-02-13 22:35:13 +00:00
/**
* @ return object Returns a new or cached connection .
*/
2016-01-24 21:14:20 +00:00
public static function get () {
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
if ( ! self :: $instance ) {
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
$credentials = Config :: get ();
2014-09-19 21:32:35 +00:00
2016-01-24 21:14:20 +00:00
self :: $instance = new self (
$credentials [ 'host' ],
$credentials [ 'user' ],
$credentials [ 'password' ],
$credentials [ 'name' ],
$credentials [ 'prefix' ]
);
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
}
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
return self :: $instance -> connection ;
2014-04-04 19:59:39 +00:00
}
2016-02-13 22:35:13 +00:00
/**
* Exits on error .
* @ return boolean Returns true when successful .
*/
2016-01-24 21:14:20 +00:00
private function __construct ( $host , $user , $password , $name = 'lychee' , $dbTablePrefix ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $host , $user , $password , $name ), __METHOD__ );
2016-01-19 10:06:54 +00:00
2016-01-30 20:43:57 +00:00
// Define the table prefix
2016-01-24 21:14:20 +00:00
defineTablePrefix ( $dbTablePrefix );
2014-04-05 16:30:24 +00:00
2016-01-30 20:43:57 +00:00
// Open a new connection to the MySQL server
2016-01-24 21:14:20 +00:00
$connection = self :: connect ( $host , $user , $password );
2014-04-05 16:30:24 +00:00
2016-01-30 20:43:57 +00:00
// Check if the connection was successful
2016-04-24 14:07:55 +00:00
if ( $connection === false ) Response :: error ( self :: connect_error ());
2014-04-05 16:30:24 +00:00
2016-02-13 22:33:39 +00:00
if ( self :: setCharset ( $connection ) === false ) Response :: error ( 'Could not set database charset!' );
2014-04-05 16:30:24 +00:00
2016-01-30 20:43:57 +00:00
// Create database
2016-02-13 22:33:39 +00:00
if ( self :: createDatabase ( $connection , $name ) === false ) Response :: error ( 'Could not create database!' );
2014-04-05 16:30:24 +00:00
2016-01-30 20:43:57 +00:00
// Create tables
2016-02-13 22:33:39 +00:00
if ( self :: createTables ( $connection ) === false ) Response :: error ( 'Could not create tables!' );
2014-04-05 16:30:24 +00:00
2016-01-30 20:43:57 +00:00
// Update database
2016-02-13 22:33:39 +00:00
if ( self :: update ( $connection , $name ) === false ) Response :: error ( 'Could not update database and tables!' );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
$this -> connection = $connection ;
2014-04-05 16:30:24 +00:00
2016-02-13 22:35:13 +00:00
return true ;
2016-01-24 21:14:20 +00:00
}
2014-04-04 19:59:39 +00:00
2016-02-13 22:35:13 +00:00
/**
* @ return object | false Returns the connection when successful .
*/
2016-01-24 21:14:20 +00:00
public static function connect ( $host = 'localhost' , $user , $password ) {
2014-08-29 18:10:50 +00:00
2016-01-30 20:43:57 +00:00
// Open a new connection to the MySQL server
2016-03-24 21:09:21 +00:00
$connection = @ new Mysqli ( $host , $user , $password );
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check if the connection was successful
2016-01-24 21:14:20 +00:00
if ( $connection -> connect_errno ) return false ;
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
return $connection ;
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
}
2016-04-24 14:07:55 +00:00
/**
* @ return string Returns the string description of the last connect error
*/
2016-04-24 14:24:48 +00:00
private static function connect_error () {
2016-04-24 14:07:55 +00:00
return mysqli_connect_error ();
2016-04-24 14:24:48 +00:00
2016-04-24 14:07:55 +00:00
}
2016-02-13 22:35:13 +00:00
/**
* @ return boolean Returns true when successful .
*/
2016-01-24 21:14:20 +00:00
private static function setCharset ( $connection ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $connection ), __METHOD__ );
2016-01-30 20:43:57 +00:00
// Avoid sql injection on older MySQL versions by using GBK
2016-01-24 21:14:20 +00:00
if ( $connection -> server_version < 50500 ) @ $connection -> set_charset ( 'GBK' );
else @ $connection -> set_charset ( 'utf8' );
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Set unicode
2016-01-31 17:49:31 +00:00
$query = 'SET NAMES utf8' ;
$result = self :: execute ( $connection , $query , null , null );
2014-04-04 19:59:39 +00:00
2016-01-31 17:49:31 +00:00
if ( $result === false ) return false ;
2014-08-07 18:23:39 +00:00
return true ;
2014-04-04 19:59:39 +00:00
}
2016-02-13 22:35:13 +00:00
/**
* @ return boolean Returns true when successful .
*/
2016-01-24 21:14:20 +00:00
public static function createDatabase ( $connection , $name = 'lychee' ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $connection ), __METHOD__ );
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check if database exists
2016-01-31 14:44:54 +00:00
if ( $connection -> select_db ( $name ) === true ) return true ;
2016-01-19 10:06:54 +00:00
2016-01-30 20:43:57 +00:00
// Create database
$query = self :: prepare ( $connection , 'CREATE DATABASE IF NOT EXISTS ?' , array ( $name ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , null , null );
2014-04-04 19:59:39 +00:00
2016-01-31 17:49:31 +00:00
if ( $result === false ) return false ;
2016-01-31 14:44:54 +00:00
if ( $connection -> select_db ( $name ) === false ) return false ;
2014-04-04 19:59:39 +00:00
return true ;
}
2016-02-13 22:35:13 +00:00
/**
* @ return boolean Returns true when successful .
*/
2016-01-24 21:14:20 +00:00
private static function createTables ( $connection ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $connection ), __METHOD__ );
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Check if tables exist
2016-01-31 17:49:31 +00:00
$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 ;
2016-01-19 10:06:54 +00:00
2016-01-31 17:49:31 +00:00
// Check if log table exists
2016-01-31 14:44:54 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_LOG ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $exist , null , null );
2016-01-31 14:44:54 +00:00
if ( $result === false ) {
2014-05-06 19:22:56 +00:00
2016-01-30 20:43:57 +00:00
// Read file
$file = __DIR__ . '/../database/log_table.sql' ;
$query = @ file_get_contents ( $file );
2014-05-06 19:22:56 +00:00
2016-01-31 14:44:54 +00:00
if ( $query === false ) return false ;
2014-08-29 21:08:18 +00:00
2016-01-30 20:43:57 +00:00
// Create table
2016-01-31 14:44:54 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_LOG ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , null , null );
2016-01-31 14:44:54 +00:00
if ( $result === false ) return false ;
2014-05-06 19:22:56 +00:00
}
2016-01-31 17:49:31 +00:00
// Check if settings table exists
2016-01-31 14:44:54 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_SETTINGS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $exist , __METHOD__ , __LINE__ );
2016-01-31 14:44:54 +00:00
if ( $result === false ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Read file
$file = __DIR__ . '/../database/settings_table.sql' ;
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2016-01-31 14:44:54 +00:00
if ( $query === false ) {
2016-01-31 17:49:31 +00:00
Log :: error ( $connection , __METHOD__ , __LINE__ , 'Could not load query for lychee_settings' );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-08-29 21:08:18 +00:00
2016-01-30 20:43:57 +00:00
// Create table
2016-01-31 14:44:54 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_SETTINGS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , __METHOD__ , __LINE__ );
if ( $result === false ) return false ;
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Read file
$file = __DIR__ . '/../database/settings_content.sql' ;
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2016-01-31 14:44:54 +00:00
if ( $query === false ) {
2016-01-31 17:49:31 +00:00
Log :: error ( $connection , __METHOD__ , __LINE__ , 'Could not load content-query for lychee_settings' );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-08-29 21:08:18 +00:00
2016-01-30 20:43:57 +00:00
// Add content
2016-01-31 14:44:54 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_SETTINGS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , __METHOD__ , __LINE__ );
if ( $result === false ) return false ;
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Generate identifier
$identifier = md5 ( microtime ( true ));
$query = self :: prepare ( $connection , " UPDATE `?` SET `value` = '?' WHERE `key` = 'identifier' LIMIT 1 " , array ( LYCHEE_TABLE_SETTINGS , $identifier ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , __METHOD__ , __LINE__ );
if ( $result === false ) return false ;
2015-06-28 15:47:19 +00:00
2014-04-04 19:59:39 +00:00
}
2016-01-31 17:49:31 +00:00
// Check if albums table exists
2016-01-31 14:44:54 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_ALBUMS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $exist , __METHOD__ , __LINE__ );
2016-01-31 14:44:54 +00:00
if ( $result === false ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Read file
$file = __DIR__ . '/../database/albums_table.sql' ;
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2016-01-31 14:44:54 +00:00
if ( $query === false ) {
2016-01-31 17:49:31 +00:00
Log :: error ( $connection , __METHOD__ , __LINE__ , 'Could not load query for lychee_albums' );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-08-29 21:08:18 +00:00
2016-01-30 20:43:57 +00:00
// Create table
2016-01-31 14:44:54 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_ALBUMS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , __METHOD__ , __LINE__ );
if ( $result === false ) return false ;
2014-04-04 19:59:39 +00:00
}
2016-01-31 17:49:31 +00:00
// Check if photos table exists
2016-01-31 14:44:54 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_PHOTOS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $exist , __METHOD__ , __LINE__ );
2016-01-31 14:44:54 +00:00
if ( $result === false ) {
2014-04-04 19:59:39 +00:00
2016-01-30 20:43:57 +00:00
// Read file
$file = __DIR__ . '/../database/photos_table.sql' ;
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2016-01-31 14:44:54 +00:00
if ( $query === false ) {
2016-01-31 17:49:31 +00:00
Log :: error ( $connection , __METHOD__ , __LINE__ , 'Could not load query for lychee_photos' );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-08-29 21:08:18 +00:00
2016-01-30 20:43:57 +00:00
// Create table
2016-01-31 14:44:54 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_PHOTOS ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , __METHOD__ , __LINE__ );
if ( $result === false ) return false ;
2014-04-04 19:59:39 +00:00
}
return true ;
}
2016-02-13 22:35:13 +00:00
/**
* Exits when an update fails .
* @ return boolean Returns true when successful .
*/
2016-01-24 21:14:20 +00:00
private static function update ( $connection , $dbName ) {
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $connection , $dbName ), __METHOD__ );
2016-01-24 21:14:20 +00:00
2016-01-30 20:43:57 +00:00
// Get current version
2016-01-31 17:49:31 +00:00
$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 ;
2016-01-24 21:14:20 +00:00
2016-01-30 20:43:57 +00:00
// For each update
2016-01-24 21:14:20 +00:00
foreach ( self :: $versions as $version ) {
2016-01-30 20:43:57 +00:00
// Only update when newer version available
2016-01-24 21:14:20 +00:00
if ( $version <= $current ) continue ;
2016-01-30 20:43:57 +00:00
// Load update
2016-01-24 21:14:20 +00:00
include ( __DIR__ . '/../database/update_' . $version . '.php' );
}
return true ;
}
2016-02-13 22:35:13 +00:00
/**
* @ return boolean Returns true when successful .
*/
2016-01-24 21:14:20 +00:00
public static function setVersion ( $connection , $version ) {
2014-08-30 17:25:01 +00:00
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $connection ), __METHOD__ );
2016-01-30 20:43:57 +00:00
$query = self :: prepare ( $connection , " UPDATE ? SET value = '?' WHERE `key` = 'version' " , array ( LYCHEE_TABLE_SETTINGS , $version ));
2016-01-31 17:49:31 +00:00
$result = self :: execute ( $connection , $query , __METHOD__ , __LINE__ );
if ( $result === false ) return false ;
2016-02-13 22:35:13 +00:00
return true ;
2014-08-30 17:25:01 +00:00
}
2016-02-13 22:35:13 +00:00
/**
* @ return string Returns a escaped query .
*/
2016-01-30 20:33:31 +00:00
public static function prepare ( $connection , $query , array $data ) {
2014-08-29 17:12:35 +00:00
2016-01-30 20:43:57 +00:00
// Check dependencies
2016-01-30 20:33:31 +00:00
Validator :: required ( isset ( $connection , $query ), __METHOD__ );
2014-08-29 17:12:35 +00:00
2016-01-30 20:43:57 +00:00
// 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 )
2014-08-31 21:49:25 +00:00
);
2016-01-31 17:49:31 +00:00
if (( $num [ 'data' ] - $num [ 'placeholder' ]) < 0 ) Log :: notice ( $connection , __METHOD__ , __LINE__ , 'Could not completely prepare query. Query has more placeholders than values.' );
2014-08-31 21:49:25 +00:00
2014-08-29 17:12:35 +00:00
foreach ( $data as $value ) {
2016-01-30 20:43:57 +00:00
// Escape
2016-01-24 21:14:20 +00:00
$value = mysqli_real_escape_string ( $connection , $value );
2014-08-29 17:12:35 +00:00
2016-01-30 20:43:57 +00:00
// Recalculate number of placeholders
2014-08-31 21:49:25 +00:00
$num [ 'placeholder' ] = substr_count ( $query , '?' );
2016-01-30 20:43:57 +00:00
// Calculate number of skips
2014-08-31 21:49:25 +00:00
if ( $num [ 'placeholder' ] > $num [ 'data' ]) $skip = $num [ 'placeholder' ] - $num [ 'data' ];
if ( $skip > 0 ) {
2016-01-30 20:43:57 +00:00
// 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 = ?)
2014-08-31 21:49:25 +00:00
$pos = - 1 ;
for ( $i = $skip ; $i > 0 ; $i -- ) $pos = strpos ( $query , '?' , $pos + 1 );
$pos ++ ;
2016-01-30 20:43:57 +00:00
$temp = substr ( $query , 0 , $pos ); // First part of $query
$query = substr ( $query , $pos ); // Last part of $query
2014-08-31 21:49:25 +00:00
}
2016-03-13 20:19:10 +00:00
// 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 );
2016-01-30 20:43:57 +00:00
// Replace
2014-08-29 17:12:35 +00:00
$query = preg_replace ( '/\?/' , $value , $query , 1 );
2014-08-31 21:49:25 +00:00
if ( $skip > 0 ) {
2014-08-29 17:12:35 +00:00
2016-01-30 20:43:57 +00:00
// Reassemble the parts of $query
2014-08-31 21:49:25 +00:00
$query = $temp . $query ;
}
2016-01-30 20:43:57 +00:00
// Reset skip
2014-08-31 21:49:25 +00:00
$skip = 0 ;
2016-01-30 20:43:57 +00:00
// Decrease number of data elements
2014-08-31 21:49:25 +00:00
$num [ 'data' ] -- ;
}
2014-08-29 17:12:35 +00:00
return $query ;
}
2016-02-13 22:35:13 +00:00
/**
* @ return object | false Returns the results on success .
*/
2016-01-31 17:49:31 +00:00
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 ;
}
2014-04-04 19:59:39 +00:00
}
2016-01-31 14:53:44 +00:00
?>