2014-04-04 19:59:39 +00:00
< ? php
###
2014-10-21 11:45:11 +00:00
# @name Database Module
2015-02-01 21:08:37 +00:00
# @copyright 2015 by Tobias Reich
2014-04-04 19:59:39 +00:00
###
if ( ! defined ( 'LYCHEE' )) exit ( 'Error: Direct access is not allowed!' );
2016-01-19 10:45:41 +00:00
final class Database extends Module {
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 (
'020700' , #2.7.0
'030000' , #3.0.0
'030001' , #3.0.1
'030003' #3.0.3
);
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-01-24 21:14:20 +00:00
private function __construct ( $host , $user , $password , $name = 'lychee' , $dbTablePrefix ) {
2014-04-04 19:59:39 +00:00
2014-04-19 19:07:36 +00:00
# Check dependencies
2016-01-24 21:14:20 +00:00
Module :: dependencies ( isset ( $host , $user , $password , $name ));
2016-01-19 10:06:54 +00:00
2016-01-24 21:14:20 +00:00
# Define the table prefix
defineTablePrefix ( $dbTablePrefix );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
# Open a new connection to the MySQL server
$connection = self :: connect ( $host , $user , $password );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
# Check if the connection was successful
if ( $connection === false ) exit ( 'Error: ' . $connection -> connect_error );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
if ( ! self :: setCharset ( $connection )) exit ( 'Error: Could not set database charset!' );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
# Create database
if ( ! self :: createDatabase ( $connection , $name )) exit ( 'Error: Could not create database!' );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
# Create tables
if ( ! self :: createTables ( $connection )) exit ( 'Error: Could not create tables!' );
2014-04-05 16:30:24 +00:00
2016-01-24 21:14:20 +00:00
# Update database
if ( ! self :: update ( $connection , $name )) exit ( '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-01-24 21:14:20 +00:00
}
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
private function __clone () {
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
# Magic method clone is empty to prevent duplication of connection
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
}
2014-08-07 18:23:39 +00:00
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-24 21:14:20 +00:00
# Open a new connection to the MySQL server
$connection = new mysqli ( $host , $user , $password );
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
# Check if the connection was successful
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
}
private static function setCharset ( $connection ) {
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
# Avoid sql injection on older MySQL versions by using GBK
if ( $connection -> server_version < 50500 ) @ $connection -> set_charset ( 'GBK' );
else @ $connection -> set_charset ( 'utf8' );
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
# Set unicode
$connection -> query ( 'SET NAMES utf8;' );
2014-04-04 19:59:39 +00:00
2014-08-07 18:23:39 +00:00
return true ;
2014-04-04 19:59:39 +00:00
}
2016-01-24 21:14:20 +00:00
public static function createDatabase ( $connection , $name = 'lychee' ) {
2014-04-04 19:59:39 +00:00
2014-04-19 19:07:36 +00:00
# Check dependencies
2016-01-24 21:14:20 +00:00
Module :: dependencies ( isset ( $connection , $name ));
2014-04-04 19:59:39 +00:00
2016-01-19 10:06:54 +00:00
# Check if database exists
2016-01-24 21:14:20 +00:00
if ( $connection -> select_db ( $name )) return true ;
2016-01-19 10:06:54 +00:00
2014-04-05 16:30:24 +00:00
# Create database
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , 'CREATE DATABASE IF NOT EXISTS ?' , array ( $name ));
$result = $connection -> query ( $query );
2014-04-04 19:59:39 +00:00
2016-01-24 21:14:20 +00:00
if ( ! $connection -> select_db ( $name )) return false ;
2014-04-04 19:59:39 +00:00
return true ;
}
2016-01-24 21:14:20 +00:00
private static function createTables ( $connection ) {
2014-04-04 19:59:39 +00:00
2014-04-19 19:07:36 +00:00
# Check dependencies
2016-01-24 21:14:20 +00:00
Module :: dependencies ( isset ( $connection ));
2014-04-04 19:59:39 +00:00
2016-01-19 10:06:54 +00:00
# Check if tables exist
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , 'SELECT * FROM ?, ?, ?, ? LIMIT 0' , array ( LYCHEE_TABLE_PHOTOS , LYCHEE_TABLE_ALBUMS , LYCHEE_TABLE_SETTINGS , LYCHEE_TABLE_LOG ));
if ( $connection -> query ( $query )) return true ;
2016-01-19 10:06:54 +00:00
2014-05-06 19:22:56 +00:00
# Create log
2016-01-24 21:14:20 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_LOG ));
if ( ! $connection -> query ( $exist )) {
2014-05-06 19:22:56 +00:00
# Read file
$file = __DIR__ . '/../database/log_table.sql' ;
$query = @ file_get_contents ( $file );
if ( ! isset ( $query ) || $query === false ) return false ;
2014-08-29 21:08:18 +00:00
# Create table
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_LOG ));
if ( ! $connection -> query ( $query )) return false ;
2014-05-06 19:22:56 +00:00
}
2014-04-04 19:59:39 +00:00
# Create settings
2016-01-24 21:14:20 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_SETTINGS ));
if ( ! $connection -> query ( $exist )) {
2014-04-04 19:59:39 +00:00
2014-04-05 16:30:24 +00:00
# Read file
2014-04-09 18:26:25 +00:00
$file = __DIR__ . '/../database/settings_table.sql' ;
2014-04-19 14:57:36 +00:00
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2014-05-30 14:54:34 +00:00
if ( ! isset ( $query ) || $query === false ) {
2016-01-24 21:14:20 +00:00
Log :: error ( __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
# Create table
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_SETTINGS ));
if ( ! $connection -> query ( $query )) {
Log :: error ( __METHOD__ , __LINE__ , $connection -> error );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-04-04 19:59:39 +00:00
2014-04-05 16:30:24 +00:00
# Read file
2014-04-09 18:26:25 +00:00
$file = __DIR__ . '/../database/settings_content.sql' ;
2014-04-19 14:57:36 +00:00
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2014-05-30 14:54:34 +00:00
if ( ! isset ( $query ) || $query === false ) {
2016-01-24 21:14:20 +00:00
Log :: error ( __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
# Add content
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_SETTINGS ));
if ( ! $connection -> query ( $query )) {
Log :: error ( __METHOD__ , __LINE__ , $connection -> error );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-04-04 19:59:39 +00:00
2015-06-28 15:47:19 +00:00
# Generate identifier
$identifier = md5 ( microtime ( true ));
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , " UPDATE `?` SET `value` = '?' WHERE `key` = 'identifier' LIMIT 1 " , array ( LYCHEE_TABLE_SETTINGS , $identifier ));
if ( ! $connection -> query ( $query )) {
Log :: error ( __METHOD__ , __LINE__ , $connection -> error );
2015-06-28 15:47:19 +00:00
return false ;
}
2014-04-04 19:59:39 +00:00
}
# Create albums
2016-01-24 21:14:20 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_ALBUMS ));
if ( ! $connection -> query ( $exist )) {
2014-04-04 19:59:39 +00:00
2014-04-05 16:30:24 +00:00
# Read file
2014-04-09 18:26:25 +00:00
$file = __DIR__ . '/../database/albums_table.sql' ;
2014-04-19 14:57:36 +00:00
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2014-05-30 14:54:34 +00:00
if ( ! isset ( $query ) || $query === false ) {
2016-01-24 21:14:20 +00:00
Log :: error ( __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
# Create table
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_ALBUMS ));
if ( ! $connection -> query ( $query )) {
Log :: error ( __METHOD__ , __LINE__ , $connection -> error );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-04-04 19:59:39 +00:00
}
# Create photos
2016-01-24 21:14:20 +00:00
$exist = self :: prepare ( $connection , 'SELECT * FROM ? LIMIT 0' , array ( LYCHEE_TABLE_PHOTOS ));
if ( ! $connection -> query ( $exist )) {
2014-04-04 19:59:39 +00:00
2014-04-05 16:30:24 +00:00
# Read file
2014-04-09 18:26:25 +00:00
$file = __DIR__ . '/../database/photos_table.sql' ;
2014-04-19 14:57:36 +00:00
$query = @ file_get_contents ( $file );
2014-04-04 19:59:39 +00:00
2014-05-30 14:54:34 +00:00
if ( ! isset ( $query ) || $query === false ) {
2016-01-24 21:14:20 +00:00
Log :: error ( __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
# Create table
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , $query , array ( LYCHEE_TABLE_PHOTOS ));
if ( ! $connection -> query ( $query )) {
Log :: error ( __METHOD__ , __LINE__ , $connection -> error );
2014-05-30 14:54:34 +00:00
return false ;
}
2014-04-04 19:59:39 +00:00
}
return true ;
}
2016-01-24 21:14:20 +00:00
private static function update ( $connection , $dbName ) {
# Check dependencies
Module :: dependencies ( isset ( $connection ));
# Get current version
$query = self :: prepare ( $connection , " SELECT * FROM ? WHERE `key` = 'version' " , array ( LYCHEE_TABLE_SETTINGS ));
$results = $connection -> query ( $query );
$current = $results -> 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 ;
}
public static function setVersion ( $connection , $version ) {
2014-08-30 17:25:01 +00:00
2016-01-24 21:14:20 +00:00
$query = self :: prepare ( $connection , " UPDATE ? SET value = '?' WHERE `key` = 'version' " , array ( LYCHEE_TABLE_SETTINGS , $version ));
$result = $connection -> query ( $query );
2014-08-30 17:25:01 +00:00
if ( ! $result ) {
2016-01-24 21:14:20 +00:00
Log :: error ( __METHOD__ , __LINE__ , 'Could not update database (' . $connection -> error . ')' );
2014-08-30 17:25:01 +00:00
return false ;
}
}
2016-01-24 21:14:20 +00:00
public static function prepare ( $connection , $query , $data ) {
2014-08-29 17:12:35 +00:00
# Check dependencies
2016-01-24 21:14:20 +00:00
Module :: dependencies ( isset ( $connection , $query , $data ));
2014-08-29 17:12:35 +00:00
2014-08-31 21:49:25 +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 ;
2015-12-07 21:44:42 +00:00
$temp = '' ;
2014-08-31 21:49:25 +00:00
$num = array (
'placeholder' => substr_count ( $query , '?' ),
'data' => count ( $data )
);
2016-01-24 21:14:20 +00:00
if (( $num [ 'data' ] - $num [ 'placeholder' ]) < 0 ) Log :: notice ( __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 ) {
# Escape
2016-01-24 21:14:20 +00:00
$value = mysqli_real_escape_string ( $connection , $value );
2014-08-29 17:12:35 +00:00
2014-08-31 21:49:25 +00:00
# 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
}
2014-08-29 17:12:35 +00:00
# Replace
$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
2014-08-31 21:49:25 +00:00
# Reassemble the parts of $query
$query = $temp . $query ;
}
# Reset skip
$skip = 0 ;
# Decrease number of data elements
$num [ 'data' ] -- ;
}
2014-08-29 17:12:35 +00:00
return $query ;
}
2014-04-04 19:59:39 +00:00
}
?>