2016-09-01 23:13:23 +02:00
< ? php
/*****************************************************************************
* Database . php
* Contains key class Database .
*
* Adapted from SMF 2.0 ' s DBA ( C ) 2011 Simple Machines
* Used under BSD 3 - clause license .
*
* Kabuki CMS ( C ) 2013 - 2015 , Aaron van Geffen
*****************************************************************************/
/**
* The database model used to communicate with the MySQL server .
*/
class Database
{
private $connection ;
private $query_count = 0 ;
private $logged_queries = [];
2022-12-25 13:50:33 +01:00
private array $db_callback ;
2016-09-01 23:13:23 +02:00
/**
* Initialises a new database connection .
* @ param server : server to connect to .
* @ param user : username to use for authentication .
* @ param password : password to use for authentication .
* @ param name : database to select .
*/
public function __construct ( $server , $user , $password , $name )
{
$this -> connection = @ mysqli_connect ( $server , $user , $password , $name );
// Give up if we have a connection error.
if ( mysqli_connect_error ())
{
header ( 'HTTP/1.1 503 Service Temporarily Unavailable' );
echo '<h2>Database Connection Problems</h2><p>Our software could not connect to the database. We apologise for any inconvenience and ask you to check back later.</p>' ;
exit ;
}
2019-09-29 14:47:56 +02:00
$this -> query ( 'SET NAMES {string:utf8}' , [ 'utf8' => 'utf8' ]);
2016-09-01 23:13:23 +02:00
}
public function getQueryCount ()
{
return $this -> query_count ;
}
public function getLoggedQueries ()
{
return $this -> logged_queries ;
}
/**
* Fetches a row from a given recordset , using field names as keys .
*/
public function fetch_assoc ( $resource )
{
return mysqli_fetch_assoc ( $resource );
}
/**
* Fetches a row from a given recordset , using numeric keys .
*/
public function fetch_row ( $resource )
{
return mysqli_fetch_row ( $resource );
}
/**
* Destroys a given recordset .
*/
public function free_result ( $resource )
{
return mysqli_free_result ( $resource );
}
public function data_seek ( $result , $row_num )
{
return mysqli_data_seek ( $result , $row_num );
}
/**
* Returns the amount of rows in a given recordset .
*/
public function num_rows ( $resource )
{
return mysqli_num_rows ( $resource );
}
/**
* Returns the amount of fields in a given recordset .
*/
public function num_fields ( $resource )
{
return mysqli_num_fields ( $resource );
}
/**
* Escapes a string .
*/
public function escape_string ( $string )
{
return mysqli_real_escape_string ( $this -> connection , $string );
}
/**
* Unescapes a string .
*/
public function unescape_string ( $string )
{
return stripslashes ( $string );
}
/**
* Returns the last MySQL error .
*/
public function error ()
{
return mysqli_error ( $this -> connection );
}
public function server_info ()
{
return mysqli_get_server_info ( $this -> connection );
}
/**
* Selects a database on a given connection .
*/
public function select_db ( $database )
{
return mysqli_select_db ( $database , $this -> connection );
}
/**
* Returns the amount of rows affected by the previous query .
*/
public function affected_rows ()
{
return mysqli_affected_rows ( $this -> connection );
}
/**
* Returns the id of the row created by a previous query .
*/
public function insert_id ()
{
return mysqli_insert_id ( $this -> connection );
}
/**
* Do a MySQL transaction .
*/
public function transaction ( $operation = 'commit' )
{
switch ( $operation )
{
case 'begin' :
case 'rollback' :
case 'commit' :
return @ mysqli_query ( $this -> connection , strtoupper ( $operation ));
default :
return false ;
}
}
/**
* Function used as a callback for the preg_match function that parses variables into database queries .
*/
private function replacement_callback ( $matches )
{
list ( $values , $connection ) = $this -> db_callback ;
if ( ! isset ( $matches [ 2 ]))
trigger_error ( 'Invalid value inserted or no type specified.' , E_USER_ERROR );
if ( ! isset ( $values [ $matches [ 2 ]]))
trigger_error ( 'The database value you\'re trying to insert does not exist: ' . htmlspecialchars ( $matches [ 2 ]), E_USER_ERROR );
$replacement = $values [ $matches [ 2 ]];
switch ( $matches [ 1 ])
{
case 'int' :
if (( ! is_numeric ( $replacement ) || ( string ) $replacement !== ( string ) ( int ) $replacement ) && $replacement !== 'NULL' )
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. Integer expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
return $replacement !== 'NULL' ? ( string ) ( int ) $replacement : 'NULL' ;
break ;
case 'string' :
case 'text' :
return $replacement !== 'NULL' ? sprintf ( '\'%1$s\'' , mysqli_real_escape_string ( $connection , $replacement )) : 'NULL' ;
break ;
case 'array_int' :
if ( is_array ( $replacement ))
{
if ( empty ( $replacement ))
trigger_error ( 'Database error, given array of integer values is empty.' , E_USER_ERROR );
foreach ( $replacement as $key => $value )
{
if ( ! is_numeric ( $value ) || ( string ) $value !== ( string ) ( int ) $value )
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. Array of integers expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
$replacement [ $key ] = ( string ) ( int ) $value ;
}
return implode ( ', ' , $replacement );
}
else
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. Array of integers expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
break ;
case 'array_string' :
if ( is_array ( $replacement ))
{
if ( empty ( $replacement ))
trigger_error ( 'Database error, given array of string values is empty.' , E_USER_ERROR );
foreach ( $replacement as $key => $value )
$replacement [ $key ] = sprintf ( '\'%1$s\'' , mysqli_real_escape_string ( $connection , $value ));
return implode ( ', ' , $replacement );
}
else
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. Array of strings expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
break ;
case 'date' :
if ( preg_match ( '~^(\d{4})-([0-1]?\d)-([0-3]?\d)$~' , $replacement , $date_matches ) === 1 )
return sprintf ( '\'%04d-%02d-%02d\'' , $date_matches [ 1 ], $date_matches [ 2 ], $date_matches [ 3 ]);
elseif ( $replacement === 'NULL' )
return 'NULL' ;
else
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. Date expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
break ;
case 'datetime' :
if ( is_a ( $replacement , 'DateTime' ))
return $replacement -> format ( '\'Y-m-d H:i:s\'' );
elseif ( preg_match ( '~^(\d{4})-([0-1]?\d)-([0-3]?\d) (\d{2}):(\d{2}):(\d{2})$~' , $replacement , $date_matches ) === 1 )
return sprintf ( '\'%04d-%02d-%02d %02d:%02d:%02d\'' , $date_matches [ 1 ], $date_matches [ 2 ], $date_matches [ 3 ], $date_matches [ 4 ], $date_matches [ 5 ], $date_matches [ 6 ]);
elseif ( $replacement === 'NULL' )
return 'NULL' ;
else
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. DateTime expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
break ;
case 'float' :
if ( ! is_numeric ( $replacement ) && $replacement !== 'NULL' )
2016-09-02 11:46:53 +02:00
trigger_error ( 'Wrong value type sent to the database for field: ' . $matches [ 2 ] . '. Floating point number expected.' , E_USER_ERROR );
2016-09-01 23:13:23 +02:00
return $replacement !== 'NULL' ? ( string ) ( float ) $replacement : 'NULL' ;
break ;
case 'identifier' :
// Backticks inside identifiers are supported as of MySQL 4.1. We don't need them here.
2019-09-29 14:47:56 +02:00
return '`' . strtr ( $replacement , [ '`' => '' , '.' => '' ]) . '`' ;
2016-09-01 23:13:23 +02:00
break ;
case 'raw' :
return $replacement ;
break ;
case 'bool' :
case 'boolean' :
// In mysql this is a synonym for tinyint(1)
return ( bool ) $replacement ? 1 : 0 ;
break ;
default :
trigger_error ( 'Undefined type <b>' . $matches [ 1 ] . '</b> used in the database query' , E_USER_ERROR );
break ;
}
}
/**
* Escapes and quotes a string using values passed , and executes the query .
*/
2019-09-29 14:47:56 +02:00
public function query ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
// One more query....
$this -> query_count ++ ;
// Overriding security? This is evil!
$security_override = $db_values === 'security_override' || ! empty ( $db_values [ 'security_override' ]);
// Please, just use new style queries.
if ( strpos ( $db_string , '\'' ) !== false && ! $security_override )
trigger_error ( 'Hack attempt!' , 'Illegal character (\') used in query.' , E_USER_ERROR );
if ( ! $security_override && ! empty ( $db_values ))
{
// Set some values for use in the callback function.
2019-09-29 14:47:56 +02:00
$this -> db_callback = [ $db_values , $this -> connection ];
2016-09-01 23:13:23 +02:00
// Insert the values passed to this function.
2019-09-29 14:47:56 +02:00
$db_string = preg_replace_callback ( '~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~' , [ & $this , 'replacement_callback' ], $db_string );
2016-09-01 23:13:23 +02:00
// Save some memory.
$this -> db_callback = [];
}
if ( defined ( " DB_LOG_QUERIES " ) && DB_LOG_QUERIES )
$this -> logged_queries [] = $db_string ;
$return = @ mysqli_query ( $this -> connection , $db_string , empty ( $this -> unbuffered ) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT );
if ( ! $return )
{
$clean_sql = implode ( " \n " , array_map ( 'trim' , explode ( " \n " , $db_string )));
trigger_error ( $this -> error () . '<br>' . $clean_sql , E_USER_ERROR );
}
return $return ;
}
/**
* Escapes and quotes a string just like db_query , but does not execute the query .
* Useful for debugging purposes .
*/
2019-09-29 14:47:56 +02:00
public function quote ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
// Please, just use new style queries.
if ( strpos ( $db_string , '\'' ) !== false )
trigger_error ( 'Hack attempt!' , 'Illegal character (\') used in query.' , E_USER_ERROR );
// Save some values for use in the callback function.
2019-09-29 14:47:56 +02:00
$this -> db_callback = [ $db_values , $this -> connection ];
2016-09-01 23:13:23 +02:00
// Insert the values passed to this function.
2019-09-29 14:47:56 +02:00
$db_string = preg_replace_callback ( '~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~' , [ & $this , 'replacement_callback' ], $db_string );
2016-09-01 23:13:23 +02:00
// Save some memory.
2019-09-29 14:47:56 +02:00
$this -> db_callback = [];
2016-09-01 23:13:23 +02:00
return $db_string ;
}
/**
* Executes a query , returning an array of all the rows it returns .
*/
2019-09-29 14:47:56 +02:00
public function queryRow ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
$row = $this -> fetch_row ( $res );
$this -> free_result ( $res );
return $row ;
}
/**
* Executes a query , returning an array of all the rows it returns .
*/
2019-09-29 14:47:56 +02:00
public function queryRows ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
2019-09-29 14:47:56 +02:00
$rows = [];
2016-09-01 23:13:23 +02:00
while ( $row = $this -> fetch_row ( $res ))
$rows [] = $row ;
$this -> free_result ( $res );
return $rows ;
}
/**
* Executes a query , returning an array of all the rows it returns .
*/
2019-09-29 14:47:56 +02:00
public function queryPair ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
2019-09-29 14:47:56 +02:00
$rows = [];
2016-09-01 23:13:23 +02:00
while ( $row = $this -> fetch_row ( $res ))
$rows [ $row [ 0 ]] = $row [ 1 ];
$this -> free_result ( $res );
return $rows ;
}
/**
* Executes a query , returning an array of all the rows it returns .
*/
2019-09-29 14:47:56 +02:00
public function queryPairs ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
2019-09-29 14:47:56 +02:00
$rows = [];
2016-09-01 23:13:23 +02:00
while ( $row = $this -> fetch_assoc ( $res ))
{
$key_value = reset ( $row );
$rows [ $key_value ] = $row ;
}
$this -> free_result ( $res );
return $rows ;
}
/**
* Executes a query , returning an associative array of all the rows it returns .
*/
2019-09-29 14:47:56 +02:00
public function queryAssoc ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
$row = $this -> fetch_assoc ( $res );
$this -> free_result ( $res );
return $row ;
}
/**
* Executes a query , returning an associative array of all the rows it returns .
*/
2019-09-29 14:47:56 +02:00
public function queryAssocs ( $db_string , $db_values = [], $connection = null )
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
2019-09-29 14:47:56 +02:00
$rows = [];
2016-09-01 23:13:23 +02:00
while ( $row = $this -> fetch_assoc ( $res ))
$rows [] = $row ;
$this -> free_result ( $res );
return $rows ;
}
/**
* Executes a query , returning the first value of the first row .
*/
2019-09-29 14:47:56 +02:00
public function queryValue ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
// If this happens, you're doing it wrong.
if ( ! $res || $this -> num_rows ( $res ) == 0 )
return null ;
list ( $value ) = $this -> fetch_row ( $res );
$this -> free_result ( $res );
return $value ;
}
/**
* Executes a query , returning an array of the first value of each row .
*/
2019-09-29 14:47:56 +02:00
public function queryValues ( $db_string , $db_values = [])
2016-09-01 23:13:23 +02:00
{
$res = $this -> query ( $db_string , $db_values );
if ( ! $res || $this -> num_rows ( $res ) == 0 )
2019-09-29 14:47:56 +02:00
return [];
2016-09-01 23:13:23 +02:00
2019-09-29 14:47:56 +02:00
$rows = [];
2016-09-01 23:13:23 +02:00
while ( $row = $this -> fetch_row ( $res ))
$rows [] = $row [ 0 ];
$this -> free_result ( $res );
return $rows ;
}
/**
* This function can be used to insert data into the database in a secure way .
*/
2022-07-08 23:52:03 +02:00
public function insert ( $method , $table , $columns , $data )
2016-09-01 23:13:23 +02:00
{
// With nothing to insert, simply return.
if ( empty ( $data ))
return ;
// Inserting data as a single row can be done as a single array.
if ( ! is_array ( $data [ array_rand ( $data )]))
2019-09-29 14:47:56 +02:00
$data = [ $data ];
2016-09-01 23:13:23 +02:00
// Create the mold for a single row insert.
$insertData = '(' ;
foreach ( $columns as $columnName => $type )
{
// Are we restricting the length?
if ( strpos ( $type , 'string-' ) !== false )
$insertData .= sprintf ( 'SUBSTRING({string:%1$s}, 1, ' . substr ( $type , 7 ) . '), ' , $columnName );
else
$insertData .= sprintf ( '{%1$s:%2$s}, ' , $type , $columnName );
}
$insertData = substr ( $insertData , 0 , - 2 ) . ')' ;
// Create an array consisting of only the columns.
$indexed_columns = array_keys ( $columns );
// Here's where the variables are injected to the query.
2019-09-29 14:47:56 +02:00
$insertRows = [];
2016-09-01 23:13:23 +02:00
foreach ( $data as $dataRow )
$insertRows [] = $this -> quote ( $insertData , array_combine ( $indexed_columns , $dataRow ));
// Determine the method of insertion.
2022-12-25 13:44:54 +01:00
$queryTitle = $method === 'replace' ? 'REPLACE' : ( $method === 'ignore' ? 'INSERT IGNORE' : 'INSERT' );
2016-09-01 23:13:23 +02:00
// Do the insert.
return $this -> query ( '
' . $queryTitle . ' INTO ' . $table . ' ( `' . implode('` , `', $indexed_columns) . '` )
VALUES
' . implode(' ,
' , $insertRows ),
2019-09-29 14:47:56 +02:00
[ 'security_override' => true ]);
2016-09-01 23:13:23 +02:00
}
}