2016-12-27 20:25:32 +00:00
|
|
|
<?php
|
2019-12-23 17:50:43 +00:00
|
|
|
defined('PROJECT_PATH') OR exit('No direct script access allowed');
|
2016-12-27 20:25:32 +00:00
|
|
|
|
2016-12-31 13:16:49 +00:00
|
|
|
// v3.43 (+ query counter)
|
2016-12-27 20:25:32 +00:00
|
|
|
class DB
|
|
|
|
{
|
|
|
|
private static $_instance = null;
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
private $_PDO;
|
|
|
|
private $_query;
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-31 13:16:49 +00:00
|
|
|
private $_query_counter;
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Handle instances
|
|
|
|
public final static function get_instance(){
|
2019-12-20 17:30:35 +00:00
|
|
|
if(self::$_instance === null){
|
2016-12-27 20:25:32 +00:00
|
|
|
self::$_instance = new static();
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
return self::$_instance;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2020-06-10 23:47:02 +00:00
|
|
|
public static function connection() {
|
2020-06-11 09:39:21 +00:00
|
|
|
return Config::get_safe('db_connection', 'sqlite');
|
2020-06-10 23:47:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-13 10:45:52 +00:00
|
|
|
// CONCAT() does not exist in SQLite, using || instead
|
2021-11-01 16:06:51 +00:00
|
|
|
// for postgres, ERROR: could not determine data type of parameter $1
|
2021-06-13 10:45:52 +00:00
|
|
|
public final static function concat(){
|
|
|
|
$values = func_get_args();
|
|
|
|
|
2021-11-01 16:06:51 +00:00
|
|
|
if(DB::connection() === 'sqlite' || DB::connection() === 'postgres') {
|
2021-06-13 10:45:52 +00:00
|
|
|
return implode(" || ", $values);
|
|
|
|
} else {
|
|
|
|
return 'CONCAT('.implode(", ", $values).')';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Initialise PDO object
|
|
|
|
private final function __construct(){
|
2020-06-11 00:03:49 +00:00
|
|
|
switch(DB::connection()) {
|
|
|
|
case 'mysql':
|
|
|
|
$this->mysql_connect();
|
|
|
|
break;
|
2021-10-29 19:56:50 +00:00
|
|
|
case 'postgres':
|
|
|
|
$this->postgres_connect();
|
|
|
|
break;
|
2020-06-11 00:03:49 +00:00
|
|
|
case 'sqlite':
|
|
|
|
$this->sqlite_connect();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final function mysql_connect(){
|
2016-12-27 20:25:32 +00:00
|
|
|
$host = Config::get_safe('mysql_host', false);
|
2019-12-23 20:20:41 +00:00
|
|
|
$port = Config::get_safe('mysql_port', false);
|
2016-12-27 20:25:32 +00:00
|
|
|
$socket = Config::get_safe('mysql_socket', false);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
if($socket === false && $host === false){
|
2019-12-23 18:09:04 +00:00
|
|
|
throw new DBException("Mysql host or socket must be defined.");
|
2016-12-27 20:25:32 +00:00
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Try to connect
|
|
|
|
try {
|
|
|
|
$this->_PDO = new \PDO(
|
|
|
|
// Server
|
2019-12-23 20:20:41 +00:00
|
|
|
'mysql:'.
|
|
|
|
($socket !== false
|
|
|
|
? 'unix_socket='.$socket
|
|
|
|
: 'host='.$host.($port !== false ? ';port='.$port : '')
|
|
|
|
).
|
2016-12-27 20:25:32 +00:00
|
|
|
// DB
|
2019-12-23 20:20:41 +00:00
|
|
|
';dbname='.Config::get('db_name').
|
2016-12-27 20:25:32 +00:00
|
|
|
// Charset
|
2019-12-23 20:20:41 +00:00
|
|
|
';charset=utf8',
|
2016-12-27 20:25:32 +00:00
|
|
|
// Username
|
|
|
|
Config::get('mysql_user'),
|
|
|
|
// Password
|
2020-06-10 23:54:39 +00:00
|
|
|
Config::get_safe('mysql_pass', ''),
|
|
|
|
// Set attributes
|
|
|
|
[
|
|
|
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
|
|
|
\PDO::ATTR_EMULATE_PREPARES => false
|
|
|
|
]
|
2016-12-27 20:25:32 +00:00
|
|
|
);
|
2020-05-26 22:14:17 +00:00
|
|
|
|
|
|
|
$this->_PDO->exec(
|
|
|
|
// Set charset
|
|
|
|
'SET NAMES utf8;'.
|
|
|
|
|
|
|
|
// Set timezone
|
|
|
|
'SET time_zone="'.date('P').'";'
|
|
|
|
);
|
2016-12-31 13:16:49 +00:00
|
|
|
} catch (PDOException $e) {
|
2016-12-27 20:25:32 +00:00
|
|
|
throw new DBException($e->getMessage());
|
|
|
|
}
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2021-10-29 19:56:50 +00:00
|
|
|
private final function postgres_connect(){
|
|
|
|
$host = Config::get_safe('postgres_host', false);
|
|
|
|
$port = Config::get_safe('postgres_port', false);
|
|
|
|
$socket = Config::get_safe('postgres_socket', false);
|
|
|
|
|
|
|
|
if($socket === false && $host === false){
|
|
|
|
throw new DBException("Postgres host or socket must be defined.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to connect
|
|
|
|
try {
|
|
|
|
$this->_PDO = new \PDO(
|
|
|
|
// Server
|
|
|
|
'pgsql:'.
|
|
|
|
($socket !== false
|
|
|
|
? 'unix_socket='.$socket
|
|
|
|
: 'host='.$host.($port !== false ? ';port='.$port : '')
|
|
|
|
).
|
|
|
|
// DB
|
|
|
|
';dbname='.Config::get('db_name').
|
|
|
|
// Charset
|
|
|
|
';options=\'--client_encoding=UTF8\'',
|
|
|
|
// Username
|
|
|
|
Config::get('postgres_user'),
|
|
|
|
// Password
|
|
|
|
Config::get_safe('postgres_pass', ''),
|
|
|
|
// Set attributes
|
|
|
|
[
|
|
|
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
|
|
|
\PDO::ATTR_EMULATE_PREPARES => false
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->_PDO->exec(
|
|
|
|
// Set timezone
|
2021-10-29 21:13:52 +00:00
|
|
|
'SET TIME ZONE "'.date('e').'";'
|
2021-10-29 19:56:50 +00:00
|
|
|
);
|
|
|
|
} catch (PDOException $e) {
|
|
|
|
throw new DBException($e->getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-11 00:03:49 +00:00
|
|
|
private final function sqlite_connect(){
|
|
|
|
$sqlite_db = PROJECT_PATH.Config::get_safe('sqlite_db', "data/sqlite.db");
|
|
|
|
|
|
|
|
// First run of sqlite
|
|
|
|
if(!file_exists($sqlite_db)) {
|
|
|
|
if(!is_writable(dirname($sqlite_db))) {
|
|
|
|
throw new DBException("Sqlite database directory must me writable.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!touch($sqlite_db)) {
|
|
|
|
throw new DBException("Cannot create sqlite database file.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inilialize SQL schema
|
|
|
|
$sql_schema = file_get_contents(APP_PATH."db/sqlite/01_schema.sql");
|
|
|
|
|
|
|
|
try {
|
|
|
|
$this->_PDO = new \PDO("sqlite:".$sqlite_db, null, null, [
|
|
|
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
|
|
|
|
]);
|
|
|
|
$this->_PDO->exec($sql_schema);
|
|
|
|
} catch (PDOException $e) {
|
|
|
|
$this->_PDO = null;
|
|
|
|
unlink($sqlite_db);
|
|
|
|
|
|
|
|
throw new DBException($e->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
return ;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to connect
|
|
|
|
try {
|
|
|
|
$this->_PDO = new \PDO("sqlite:".$sqlite_db, null, null, [
|
|
|
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
|
|
|
|
]);
|
|
|
|
} catch (PDOException $e) {
|
|
|
|
throw new DBException($e->getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Just flattern array to be binded : [key1, key2, [key3, [key4]]] => [key1, key2, key3, key4]
|
|
|
|
private final function bind_value($key, $value){
|
|
|
|
if(is_array($value)){
|
|
|
|
foreach($value as $one_value){
|
|
|
|
$key = $this->bind_value($key, $one_value);
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
return $key;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2020-01-08 22:28:40 +00:00
|
|
|
// BUG: Force strings to be UTF-8
|
|
|
|
// remove all 4-bytes characters.
|
|
|
|
if(is_string($value)){
|
|
|
|
$value = preg_replace('/[\xF0-\xF7].../s', '', $value);
|
|
|
|
}
|
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
$this->_query->bindValue($key, $value);
|
|
|
|
return ++$key;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Process Query
|
|
|
|
// query ($sql)
|
|
|
|
// query ($sql, $bind_param_01, $bind_param_02, ...)
|
|
|
|
// query ($sql, [$bind_param_01, $bind_param_02, ...])
|
|
|
|
public final function query(){
|
|
|
|
// Second parm is binded values
|
|
|
|
$params = func_get_args();
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// First parameter is sql
|
|
|
|
$sql = $params[0];
|
|
|
|
unset($params[0]);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2021-10-29 19:56:50 +00:00
|
|
|
// Replace backticks with " for postgres
|
|
|
|
if(DB::connection() === 'postgres') {
|
|
|
|
$sql = str_replace("`", '"', $sql);
|
|
|
|
}
|
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Debug mode
|
|
|
|
if(Config::get_safe('debug', false)){
|
|
|
|
echo "<!-- ".$sql." + ".json_encode($params)." -->\n";
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Try to prepare MySQL statement
|
|
|
|
try {
|
|
|
|
// Prepare PDO statement
|
|
|
|
$this->_query = $this->_PDO->prepare($sql);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Bind values
|
|
|
|
$this->bind_value(1, $params);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Execute
|
|
|
|
$this->_query->execute();
|
2016-12-31 13:16:49 +00:00
|
|
|
} catch (PDOException $e) {
|
2016-12-27 20:25:32 +00:00
|
|
|
throw new DBException($e->getMessage());
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-31 13:16:49 +00:00
|
|
|
$this->_query_counter++;
|
2016-12-27 20:25:32 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Insert into table
|
|
|
|
public final function insert($table_name, $fields = null){
|
|
|
|
// If empty line
|
|
|
|
if(empty($fields)){
|
|
|
|
return $this->query("INSERT INTO `{$table_name}` () VALUES ()");
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// If multiple
|
|
|
|
if(isset($fields[0])){
|
|
|
|
// Turn array into PDO prepered statement format
|
|
|
|
$keys = array_keys($fields[0]);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Build query
|
|
|
|
$query = "INSERT INTO `{$table_name}` (`".implode('`, `', $keys)."`) VALUES ";
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Insert values
|
|
|
|
$first = true;
|
|
|
|
$prepared_data = array();
|
|
|
|
foreach($fields as $field){
|
|
|
|
if($first){
|
|
|
|
$first = false;
|
|
|
|
} else {
|
|
|
|
$query .= ',';
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
end($field);
|
|
|
|
$last_key = key($field);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
$query .= '(';
|
|
|
|
foreach($field as $key => $value){
|
|
|
|
if($value === "NOW()"){
|
2020-06-10 23:47:02 +00:00
|
|
|
if(DB::connection() === 'sqlite') {
|
|
|
|
$query .= "datetime('now', 'localtime')";
|
|
|
|
} else {
|
|
|
|
$query .= "NOW()";
|
|
|
|
}
|
2016-12-27 20:25:32 +00:00
|
|
|
} else {
|
|
|
|
$query .= '?';
|
|
|
|
$prepared_data[] = $value;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
if($last_key != $key){
|
|
|
|
$query .= ',';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$query .= ')';
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Execute query
|
|
|
|
return $this->query($query, $prepared_data);
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// If only single
|
|
|
|
return $this->insert($table_name, array($fields));
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Update table
|
|
|
|
// update ($table_name, $fields)
|
|
|
|
// update ($table_name, $fields, $sql)
|
|
|
|
// update ($table_name, $fields, $sql, $bind_param_01, $bind_param_02, ...)
|
|
|
|
// update ($table_name, $fields, $sql, [$bind_param_01, $bind_param_02, ...])
|
|
|
|
public final function update(){
|
|
|
|
// Fourt param is binded values
|
|
|
|
$params = func_get_args();
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// First is table_name
|
|
|
|
$table_name = $params[0];
|
|
|
|
unset($params[0]);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Second is fields
|
|
|
|
$fields = $params[1];
|
|
|
|
unset($params[1]);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Third is sql
|
|
|
|
$sql = $params[2];
|
|
|
|
unset($params[2]);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// If fields are not array, do nothing
|
|
|
|
if(!is_array($fields)){
|
|
|
|
return $this;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
end($fields);
|
|
|
|
$last_key = key($fields);
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Support for NOW()
|
|
|
|
$prepared_data = array();
|
|
|
|
$set_data = null;
|
|
|
|
foreach($fields as $key => $value){
|
|
|
|
if($value === "NOW()"){
|
2020-06-10 23:47:02 +00:00
|
|
|
if(DB::connection() === 'sqlite') {
|
|
|
|
$set_data .="`{$key}` = datetime('now', 'localtime')";
|
|
|
|
} else {
|
|
|
|
$set_data .="`{$key}` = NOW()";
|
|
|
|
}
|
2016-12-27 20:25:32 +00:00
|
|
|
} else {
|
|
|
|
$set_data .= "`{$key}` = ?";
|
|
|
|
$prepared_data[] = $value;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
if($last_key != $key){
|
|
|
|
$set_data .= ',';
|
|
|
|
}
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// If params are not array, make it
|
|
|
|
if(!is_array($params)){
|
|
|
|
$params = array($params);
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Merge fields array and additional SQL data
|
|
|
|
foreach($params as $param){
|
|
|
|
$prepared_data[] = $param;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Build query
|
|
|
|
$query = "UPDATE `{$table_name}` SET {$set_data} ".$sql;
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Execute query
|
|
|
|
return $this->query($query, $prepared_data);
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Alias for all
|
|
|
|
public final function results(){
|
|
|
|
trigger_error("Using deprecated method <strong>DB::results();</strong>. Use <strong>DB::all();</strong> instead.");
|
|
|
|
return $this->all();
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Get all rows
|
|
|
|
public final function all($type = \PDO::FETCH_ASSOC){
|
|
|
|
return $this->_query->fetchAll($type);
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Get all values to one dimensional array
|
|
|
|
public final function columns($column = 0){
|
|
|
|
return $this->_query->fetchAll(\PDO::FETCH_COLUMN, $column);
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Get first row from result
|
|
|
|
public final function first($key = null){
|
|
|
|
$results = $this->all();
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
if($key !== null){
|
|
|
|
return @$results[0][$key];
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
return @$results[0];
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Get last inserted ID
|
|
|
|
public final function last_id(){
|
|
|
|
return $this->_PDO->lastInsertId();
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-27 20:25:32 +00:00
|
|
|
// Exec
|
|
|
|
public final function exec($sql){
|
|
|
|
// Try to execute MySQL
|
|
|
|
try {
|
|
|
|
$this->_PDO->exec($sql);
|
2016-12-31 13:16:49 +00:00
|
|
|
} catch (PDOException $e) {
|
2016-12-27 20:25:32 +00:00
|
|
|
throw new DBException($e->getMessage());
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-31 13:16:49 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2019-12-20 17:38:48 +00:00
|
|
|
|
2016-12-31 13:16:49 +00:00
|
|
|
public final function total_queries(){
|
|
|
|
return $this->_query_counter;
|
2016-12-27 20:25:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle DB errors
|
|
|
|
class DBException extends Exception{}
|