mirror of
https://framagit.org/hubzilla/core.git
synced 2026-06-21 00:52:33 -04:00
The NULL_DATE constant is defined conditionally in the DBA static class. This causes issues with static analyzing tools like PHPStan, because they can not really know if the constant is defined or not. We could make PHPStan ignore this, but since there already is a `get_null_date()` method on the `dba_driver` class, this patch changes the code to use this method instead. We could also use the public static attribute `$null_date` on the DBA class directly, but using a method feels cleaner, and allows for making the attribute private, or even removing it completely at some later time. I'm not removing the NULL_DATE constant for now, in case it is in use by any extensions.
528 lines
12 KiB
PHP
528 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* @file dba_driver.php
|
|
* @brief Some database related functions and database classes.
|
|
*
|
|
* This file contains the abstract database driver class dba_driver, the
|
|
* database class DBA and some functions for working with databases.
|
|
*/
|
|
|
|
/**
|
|
* @brief Database classs with database factory method.
|
|
*
|
|
* The factory will return a database driver which is an implementation of the
|
|
* abstract dba_driver class.
|
|
*/
|
|
class DBA {
|
|
|
|
static public $dba = null;
|
|
static public $dbtype = null;
|
|
static public $scheme = 'mysql';
|
|
static public $logging = false;
|
|
|
|
static public $install_script = 'schema_mysql.sql';
|
|
static public $null_date = '0001-01-01 00:00:00';
|
|
static public $utc_now = 'UTC_TIMESTAMP()';
|
|
static public $tquot = "`";
|
|
|
|
|
|
/**
|
|
* @brief Returns the database driver object.
|
|
*
|
|
* @param string $server DB server name (or PDO dsn - e.g. mysqli:foobar.com;)
|
|
* @param string $port DB port
|
|
* @param string $user DB username
|
|
* @param string $pass DB password
|
|
* @param string $db database name
|
|
* @param string $dbtype 0 for mysql, 1 for postgres
|
|
* @param bool $install Defaults to false
|
|
* @return null|dba_driver A database driver object (dba_pdo) or null if no driver found.
|
|
*/
|
|
static public function dba_factory($server,$port,$user,$pass,$db,$dbtype,$db_charset,$install = false) {
|
|
|
|
self::$dba = null;
|
|
self::$dbtype = intval($dbtype);
|
|
|
|
if(self::$dbtype == DBTYPE_POSTGRES) {
|
|
if(!($port))
|
|
$port = 5432;
|
|
|
|
self::$install_script = 'schema_postgres.sql';
|
|
self::$utc_now = "now() at time zone 'UTC'";
|
|
self::$tquot = '"';
|
|
self::$scheme = 'pgsql';
|
|
}
|
|
else {
|
|
|
|
// attempt to use the pdo driver compiled-in mysqli socket
|
|
// if using 'localhost' with no port configured.
|
|
// If this is wrong you'll need to set the socket path specifically
|
|
// using a server name of 'mysql:unix_socket=/socket/path', setting /socket/path
|
|
// as needed for your platform
|
|
|
|
if((!($port)) && ($server !== 'localhost'))
|
|
$port = 3306;
|
|
}
|
|
|
|
require_once('include/dba/dba_pdo.php');
|
|
self::$dba = new dba_pdo($server,self::$scheme,$port,$user,$pass,$db,$db_charset,$install);
|
|
|
|
if (!defined('NULL_DATE')) {
|
|
define('NULL_DATE', self::$null_date);
|
|
}
|
|
|
|
if (!defined('ACTIVE_DBTYPE')) {
|
|
define('ACTIVE_DBTYPE', self::$dbtype);
|
|
}
|
|
|
|
if (!defined('TQUOT')) {
|
|
define('TQUOT', self::$tquot);
|
|
}
|
|
|
|
return self::$dba;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Abstract database driver class.
|
|
*
|
|
* This class gets extended by the real database driver class. We used to have
|
|
* dba_mysql, dba_mysqli or dba_postgres, but we moved to PDO and the only
|
|
* implemented driver is dba_pdo.
|
|
*/
|
|
abstract class dba_driver {
|
|
// legacy behavior
|
|
|
|
public $db;
|
|
|
|
public $debug = 0;
|
|
public $connected = false;
|
|
public $error = false;
|
|
|
|
/**
|
|
* Connects to the database.
|
|
*
|
|
* This abstract function needs to be implemented in the real driver.
|
|
*
|
|
* @return bool
|
|
*/
|
|
abstract function connect(): bool;
|
|
|
|
/**
|
|
* @brief Perform a DB query with the SQL statement $sql.
|
|
*
|
|
* This abstract function needs to be implemented in the real driver.
|
|
*
|
|
* @param string $sql The SQL query to execute
|
|
*/
|
|
abstract function q($sql);
|
|
|
|
/**
|
|
* @brief Escape a string before being passed to a DB query.
|
|
*
|
|
* This abstract function needs to be implemented in the real driver.
|
|
*
|
|
* @param string $str The string to escape.
|
|
*/
|
|
abstract function escape($str);
|
|
|
|
/**
|
|
* @brief Close the database connection.
|
|
*
|
|
* This abstract function needs to be implemented in the real driver.
|
|
*/
|
|
abstract function close();
|
|
|
|
/**
|
|
* @brief Return text name for db driver
|
|
*
|
|
* This abstract function needs to be implemented in the real driver.
|
|
*/
|
|
abstract function getdriver();
|
|
|
|
function __construct(
|
|
readonly string $server,
|
|
readonly string $scheme,
|
|
readonly string $port,
|
|
readonly string $user,
|
|
protected string $pass,
|
|
readonly string $dbname,
|
|
readonly string $db_charset,
|
|
$install = false)
|
|
{
|
|
if ($install && ! $this->install()) {
|
|
return;
|
|
}
|
|
$this->connect();
|
|
}
|
|
|
|
function get_null_date() {
|
|
return \DBA::$null_date;
|
|
}
|
|
|
|
public static function get_install_script() {
|
|
$platform_name = \Zotlabs\Lib\System::get_platform_name();
|
|
if(file_exists('install/' . $platform_name . '/' . \DBA::$install_script))
|
|
return 'install/' . $platform_name . '/' . \DBA::$install_script;
|
|
|
|
return 'install/' . \DBA::$install_script;
|
|
}
|
|
|
|
function get_table_quote() {
|
|
return \DBA::$tquot;
|
|
}
|
|
|
|
function utcnow() {
|
|
return \DBA::$utc_now;
|
|
}
|
|
|
|
function install() {
|
|
if (!strlen($this->server) && strlen($this->user)) {
|
|
$this->connected = false;
|
|
$this->db = null;
|
|
return false;
|
|
}
|
|
|
|
// if(strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1') && (! strpbrk($server,':;'))) {
|
|
// if(! z_dns_check($server)) {
|
|
// $this->error = sprintf( t('Cannot locate DNS info for database server \'%s\''), $server);
|
|
// $this->connected = false;
|
|
// $this->db = null;
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the database driver's debugging state.
|
|
*
|
|
* @param int $dbg 0 to disable debugging
|
|
*/
|
|
function dbg($dbg) {
|
|
$this->debug = $dbg;
|
|
}
|
|
|
|
function __destruct() {
|
|
if($this->db && $this->connected) {
|
|
$this->close();
|
|
}
|
|
}
|
|
|
|
function quote_interval($txt) {
|
|
return $txt;
|
|
}
|
|
|
|
function optimize_table($table) {
|
|
q('OPTIMIZE TABLE '.$table);
|
|
}
|
|
|
|
function concat($fld, $sep) {
|
|
return 'GROUP_CONCAT(DISTINCT '.$fld.' SEPARATOR \''.$sep.'\')';
|
|
}
|
|
|
|
function escapebin($str) {
|
|
return $this->escape($str);
|
|
}
|
|
|
|
function unescapebin($str) {
|
|
return $str;
|
|
}
|
|
|
|
} // end abstract dba_driver class
|
|
|
|
|
|
//
|
|
// Procedural functions
|
|
//
|
|
|
|
function printable($s) {
|
|
$s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s);
|
|
$s = str_replace("\x00",'.',$s);
|
|
if(x($_SERVER,'SERVER_NAME'))
|
|
$s = escape_tags($s);
|
|
|
|
return $s;
|
|
}
|
|
|
|
/**
|
|
* @brief set database driver debugging state.
|
|
*
|
|
* @param int $state 0 to disable debugging
|
|
*/
|
|
function dbg($state) {
|
|
global $db;
|
|
|
|
if(\DBA::$dba)
|
|
\DBA::$dba->dbg($state);
|
|
}
|
|
|
|
/**
|
|
* @brief Escape strings being passed to DB queries.
|
|
*
|
|
* Always escape strings being used in DB queries. This function returns the
|
|
* escaped string. Integer DB parameters should all be proven integers by
|
|
* wrapping with intval().
|
|
*
|
|
* @param string $str A string to pass to a DB query
|
|
* @return string Return an escaped string of the value to pass to a DB query.
|
|
*/
|
|
function dbesc($str) {
|
|
|
|
if(is_null_date($str))
|
|
$str = DBA::$dba->get_null_date();
|
|
|
|
if(\DBA::$dba && \DBA::$dba->connected)
|
|
return(\DBA::$dba->escape($str));
|
|
else
|
|
return(str_replace("'", "\\'", $str));
|
|
}
|
|
function dbescbin($str) {
|
|
return \DBA::$dba->escapebin($str);
|
|
}
|
|
|
|
function dbunescbin($str) {
|
|
return \DBA::$dba->unescapebin($str);
|
|
}
|
|
|
|
function dbescdate($date) {
|
|
if(is_null_date($date))
|
|
return \DBA::$dba->escape(DBA::$dba->get_null_date());
|
|
|
|
return \DBA::$dba->escape($date);
|
|
}
|
|
|
|
function db_quoteinterval($txt) {
|
|
return \DBA::$dba->quote_interval($txt);
|
|
}
|
|
|
|
function dbesc_identifier($str) {
|
|
return \DBA::$dba->escape_identifier($str);
|
|
}
|
|
|
|
function db_utcnow() {
|
|
return \DBA::$dba->utcnow();
|
|
}
|
|
|
|
function db_optimizetable($table) {
|
|
\DBA::$dba->optimize_table($table);
|
|
}
|
|
|
|
function db_concat($fld, $sep) {
|
|
return \DBA::$dba->concat($fld, $sep);
|
|
}
|
|
|
|
function db_use_index($str) {
|
|
return \DBA::$dba->use_index($str);
|
|
}
|
|
|
|
function db_str_to_date($str) {
|
|
return \DBA::$dba->str_to_date($str);
|
|
}
|
|
|
|
/**
|
|
* @brief Execute a SQL query with printf style args.
|
|
*
|
|
* printf style arguments %s and %d are replaced with variable arguments, which
|
|
* should each be appropriately dbesc() or intval().
|
|
*
|
|
* SELECT queries return an array of results or false if SQL or DB error. Other
|
|
* queries return true if the command was successful or false if it wasn't.
|
|
*
|
|
* Example:
|
|
* @code{.php}$r = q("SELECT * FROM %s WHERE `uid` = %d",
|
|
* 'user', 1);@endcode
|
|
*
|
|
* @param string $sql The SQL query to execute
|
|
* @return bool|array
|
|
*/
|
|
function q($sql) {
|
|
|
|
$args = func_get_args();
|
|
array_shift($args);
|
|
|
|
if(\DBA::$dba && \DBA::$dba->connected) {
|
|
$stmt = vsprintf($sql, $args);
|
|
if($stmt === false) {
|
|
db_logger('dba: vsprintf error: ' .
|
|
print_r(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 1), true),LOGGER_NORMAL,LOG_CRIT);
|
|
}
|
|
if(\DBA::$dba->debug)
|
|
db_logger('Sql: ' . $stmt, LOGGER_DEBUG, LOG_INFO);
|
|
|
|
return \DBA::$dba->q($stmt);
|
|
}
|
|
|
|
/*
|
|
* This will happen occasionally trying to store the
|
|
* session data after abnormal program termination
|
|
*/
|
|
|
|
db_logger('dba: no database: ' . print_r($args,true),LOGGER_NORMAL,LOG_CRIT);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Raw DB query, no arguments.
|
|
*
|
|
* This function executes a raw DB query without any arguments.
|
|
*
|
|
* @param string $sql The SQL query to execute
|
|
*/
|
|
function dbq($sql) {
|
|
|
|
if(\DBA::$dba && \DBA::$dba->connected)
|
|
$ret = \DBA::$dba->q($sql);
|
|
else
|
|
$ret = false;
|
|
|
|
return $ret;
|
|
}
|
|
|
|
|
|
|
|
// Caller is responsible for ensuring that any integer arguments to
|
|
// dbesc_array are actually integers and not malformed strings containing
|
|
// SQL injection vectors. All integer array elements should be specifically
|
|
// cast to int to avoid trouble.
|
|
|
|
function dbesc_array_cb(&$item, $key) {
|
|
if(is_string($item)) {
|
|
if(is_null_date($item))
|
|
$item = DBA::$dba->get_null_date();
|
|
$item = dbesc($item);
|
|
}
|
|
}
|
|
|
|
|
|
function dbesc_array(&$arr) {
|
|
$bogus_key = false;
|
|
if(is_array($arr) && count($arr)) {
|
|
$matches = false;
|
|
foreach($arr as $k => $v) {
|
|
if(preg_match('/([^a-zA-Z0-9\-\_\.])/',$k,$matches)) {
|
|
logger('bogus key: ' . $k);
|
|
$bogus_key = true;
|
|
}
|
|
}
|
|
array_walk($arr,'dbesc_array_cb');
|
|
if($bogus_key) {
|
|
$arr['BOGUS.KEY'] = 1;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function db_getfunc($f) {
|
|
$lookup = array(
|
|
'rand'=>array(
|
|
DBTYPE_MYSQL=>'RAND()',
|
|
DBTYPE_POSTGRES=>'RANDOM()'
|
|
),
|
|
'utc_timestamp'=>array(
|
|
DBTYPE_MYSQL=>'UTC_TIMESTAMP()',
|
|
DBTYPE_POSTGRES=>"now() at time zone 'UTC'"
|
|
),
|
|
'regexp'=>array(
|
|
DBTYPE_MYSQL=>'REGEXP',
|
|
DBTYPE_POSTGRES=>'~'
|
|
),
|
|
'^'=>array(
|
|
DBTYPE_MYSQL=>'^',
|
|
DBTYPE_POSTGRES=>'#'
|
|
)
|
|
);
|
|
$f = strtolower($f);
|
|
if(isset($lookup[$f]) && isset($lookup[$f][ACTIVE_DBTYPE]))
|
|
return $lookup[$f][ACTIVE_DBTYPE];
|
|
|
|
db_logger('Unable to abstract DB function "'. $f . '" for dbtype ' . ACTIVE_DBTYPE, LOGGER_DEBUG, LOG_ERR);
|
|
return $f;
|
|
}
|
|
|
|
function db_load_file($f) {
|
|
// db errors should get logged to the logfile
|
|
$str = @file_get_contents($f);
|
|
$arr = explode(';', $str);
|
|
if($arr) {
|
|
foreach($arr as $a) {
|
|
if(strlen(trim($a))) {
|
|
$r = dbq(trim($a));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// The logger function may make DB calls internally to query the system logging parameters.
|
|
// This can cause a recursion if database debugging is enabled.
|
|
// So this function preserves the current database debugging state and then turns it off
|
|
// temporarily while doing the logger() call
|
|
|
|
function db_logger($s,$level = LOGGER_NORMAL,$syslog = LOG_INFO) {
|
|
|
|
if(\DBA::$logging || ! \DBA::$dba)
|
|
return;
|
|
|
|
$saved = \DBA::$dba->debug;
|
|
\DBA::$dba->debug = false;
|
|
\DBA::$logging = true;
|
|
logger($s,$level,$syslog);
|
|
\DBA::$logging = false;
|
|
\DBA::$dba->debug = $saved;
|
|
}
|
|
|
|
|
|
function db_columns($table) {
|
|
|
|
if($table) {
|
|
if(ACTIVE_DBTYPE === DBTYPE_POSTGRES) {
|
|
$r = q("SELECT column_name as field FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '%s'",
|
|
dbesc($table)
|
|
);
|
|
if($r) {
|
|
return ids_to_array($r,'field');
|
|
}
|
|
}
|
|
else {
|
|
$r = q("show columns in %s",
|
|
dbesc($table)
|
|
);
|
|
if($r) {
|
|
return ids_to_array($r,'Field');
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
|
|
function db_indexes($table) {
|
|
|
|
if($table) {
|
|
if(ACTIVE_DBTYPE === DBTYPE_POSTGRES) {
|
|
$r = q("SELECT indexname from pg_indexes where tablename = '%s'",
|
|
dbesc($table)
|
|
);
|
|
if($r) {
|
|
return ids_to_array($r,'indexname');
|
|
}
|
|
}
|
|
else {
|
|
$r = q("show index from %s",
|
|
dbesc($table)
|
|
);
|
|
if($r) {
|
|
return ids_to_array($r,'Key_name');
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|