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
'030100' // 3.1.0
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
*/
public static function connect_error () {
return mysqli_connect_error ();
}
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
?>