Current File : /home/escuelai/public_html/it/src/DBmysql.php |
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2022 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
use Glpi\Application\ErrorHandler;
use Glpi\System\Requirement\DbTimezones;
use Glpi\Toolbox\Sanitizer;
/**
* Database class for Mysql
**/
class DBmysql
{
//! Database Host - string or Array of string (round robin)
public $dbhost = "";
//! Database User
public $dbuser = "";
//! Database Password
public $dbpassword = "";
//! Default Database
public $dbdefault = "";
/**
* The database handler
* @var mysqli
*/
protected $dbh;
//! Database Error
public $error = 0;
// Slave management
public $slave = false;
private $in_transaction;
/**
* Defines if connection must use SSL.
*
* @var boolean
*/
public $dbssl = false;
/**
* The path name to the key file (used in case of SSL connection).
*
* @see mysqli::ssl_set()
* @var string|null
*/
public $dbsslkey = null;
/**
* The path name to the certificate file (used in case of SSL connection).
*
* @see mysqli::ssl_set()
* @var string|null
*/
public $dbsslcert = null;
/**
* The path name to the certificate authority file (used in case of SSL connection).
*
* @see mysqli::ssl_set()
* @var string|null
*/
public $dbsslca = null;
/**
* The pathname to a directory that contains trusted SSL CA certificates in PEM format
* (used in case of SSL connection).
*
* @see mysqli::ssl_set()
* @var string|null
*/
public $dbsslcapath = null;
/**
* A list of allowable ciphers to use for SSL encryption (used in case of SSL connection).
*
* @see mysqli::ssl_set()
* @var string|null
*/
public $dbsslcacipher = null;
/**
* Determine if timezones should be used for timestamp fields.
* Defaults to false to keep backward compatibility with old DB.
*
* @var bool
*/
public $use_timezones = false;
/**
* Determine if warnings related to MySQL deprecations should be logged too.
* Defaults to false as this option should only on development/test environment.
*
* @var bool
*/
public $log_deprecation_warnings = false;
/**
* Determine if utf8mb4 should be used for DB connection and tables altering operations.
* Defaults to false to keep backward compatibility with old DB.
*
* @var bool
*/
public $use_utf8mb4 = false;
/**
* Determine if MyISAM engine usage should be allowed for tables creation/altering operations.
* Defaults to true to keep backward compatibility with old DB.
*
* @var bool
*/
public $allow_myisam = true;
/**
* Determine if datetime fields usage should be allowed for tables creation/altering operations.
* Defaults to true to keep backward compatibility with old DB.
*
* @var bool
*/
public $allow_datetime = true;
/**
* Determine if signed integers in primary/foreign keys usage should be allowed for tables creation/altering operations.
* Defaults to true to keep backward compatibility with old DB.
*
* @var bool
*/
public $allow_signed_keys = true;
/** Is it a first connection ?
* Indicates if the first connection attempt is successful or not
* if first attempt fail -> display a warning which indicates that glpi is in readonly
**/
public $first_connection = true;
// Is connected to the DB ?
public $connected = false;
//to calculate execution time
public $execution_time = false;
private $cache_disabled = false;
/**
* Cached list fo tables.
*
* @var array
* @see self::tableExists()
*/
private $table_cache = [];
/**
* Cached list of fields.
*
* @var array
* @see self::listFields()
*/
private $field_cache = [];
/**
* Last query warnings.
*
* @var array
*/
private $last_query_warnings = [];
/**
* Constructor / Connect to the MySQL Database
*
* @param integer $choice host number (default NULL)
*
* @return void
*/
public function __construct($choice = null)
{
$this->connect($choice);
}
/**
* Connect using current database settings
* Use dbhost, dbuser, dbpassword and dbdefault
*
* @param integer $choice host number (default NULL)
*
* @return void
*/
public function connect($choice = null)
{
$this->connected = false;
// Do not trigger errors nor throw exceptions at PHP level
// as we already extract error and log while fetching result.
mysqli_report(MYSQLI_REPORT_OFF);
$this->dbh = @new mysqli();
if ($this->dbssl) {
$this->dbh->ssl_set(
$this->dbsslkey,
$this->dbsslcert,
$this->dbsslca,
$this->dbsslcapath,
$this->dbsslcacipher
);
}
if (is_array($this->dbhost)) {
// Round robin choice
$i = (isset($choice) ? $choice : mt_rand(0, count($this->dbhost) - 1));
$host = $this->dbhost[$i];
} else {
$host = $this->dbhost;
}
$hostport = explode(":", $host);
if (count($hostport) < 2) {
// Host
$this->dbh->real_connect($host, $this->dbuser, rawurldecode($this->dbpassword), $this->dbdefault);
} else if (intval($hostport[1]) > 0) {
// Host:port
$this->dbh->real_connect($hostport[0], $this->dbuser, rawurldecode($this->dbpassword), $this->dbdefault, $hostport[1]);
} else {
// :Socket
$this->dbh->real_connect($hostport[0], $this->dbuser, rawurldecode($this->dbpassword), $this->dbdefault, ini_get('mysqli.default_port'), $hostport[1]);
}
if ($this->dbh->connect_error) {
$this->connected = false;
$this->error = 1;
} else if (!defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
$this->connected = false;
$this->error = 2;
} else {
$this->setConnectionCharset();
// force mysqlnd to return int and float types correctly (not as strings)
$this->dbh->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
$this->dbh->query("SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''))");
$this->connected = true;
$this->setTimezone($this->guessTimezone());
}
}
/**
* Guess timezone
*
* Will check for an existing loaded timezone from user,
* then will check in preferences and finally will fallback to system one.
*
* @return string
*
* @since 9.5.0
*/
public function guessTimezone()
{
if ($this->use_timezones) {
if (isset($_SESSION['glpi_tz'])) {
$zone = $_SESSION['glpi_tz'];
} else {
$conf_tz = ['value' => null];
if (
$this->tableExists(Config::getTable())
&& $this->fieldExists(Config::getTable(), 'value')
) {
$conf_tz = $this->request([
'SELECT' => 'value',
'FROM' => Config::getTable(),
'WHERE' => [
'context' => 'core',
'name' => 'timezone'
]
])->current();
}
$zone = !empty($conf_tz['value']) ? $conf_tz['value'] : date_default_timezone_get();
}
} else {
$zone = date_default_timezone_get();
}
return $zone;
}
/**
* Escapes special characters in a string for use in an SQL statement,
* taking into account the current charset of the connection
*
* @since 0.84
*
* @param string $string String to escape
*
* @return string escaped string
*/
public function escape($string)
{
if (!is_string($string)) {
return $string;
}
return $this->dbh->real_escape_string($string);
}
/**
* Execute a MySQL query
*
* @param string $query Query to execute
*
* @var array $CFG_GLPI
* @var array $DEBUG_SQL
* @var integer $SQL_TOTAL_REQUEST
*
* @return mysqli_result|boolean Query result handler
*/
public function query($query)
{
global $CFG_GLPI, $DEBUG_SQL, $SQL_TOTAL_REQUEST;
$is_debug = isset($_SESSION['glpi_use_mode']) && ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE);
if ($is_debug && $CFG_GLPI["debug_sql"]) {
$SQL_TOTAL_REQUEST++;
$DEBUG_SQL["queries"][$SQL_TOTAL_REQUEST] = $query;
}
if ($is_debug && $CFG_GLPI["debug_sql"] || $this->execution_time === true) {
$TIMER = new Timer();
$TIMER->start();
}
$this->checkForDeprecatedTableOptions($query);
$res = $this->dbh->query($query);
if (!$res) {
// no translation for error logs
$error = " *** MySQL query error:\n SQL: " . $query . "\n Error: " .
$this->dbh->error . "\n";
$error .= Toolbox::backtrace(false, 'DBmysql->query()', ['Toolbox::backtrace()']);
Toolbox::logSqlError($error);
ErrorHandler::getInstance()->handleSqlError($this->dbh->errno, $this->dbh->error, $query);
if (($is_debug || isAPI()) && $CFG_GLPI["debug_sql"]) {
$DEBUG_SQL["errors"][$SQL_TOTAL_REQUEST] = $this->error();
}
}
if ($is_debug && $CFG_GLPI["debug_sql"]) {
$TIME = $TIMER->getTime();
$DEBUG_SQL["times"][$SQL_TOTAL_REQUEST] = $TIME;
$DEBUG_SQL['rows'][$SQL_TOTAL_REQUEST] = $this->affectedRows();
}
$this->last_query_warnings = $this->fetchQueryWarnings();
$DEBUG_SQL['warnings'][$SQL_TOTAL_REQUEST] = $this->last_query_warnings;
// Output warnings in SQL log
if (!empty($this->last_query_warnings)) {
$message = sprintf(
" *** MySQL query warnings:\n SQL: %s\n Warnings: \n%s\n",
$query,
implode(
"\n",
array_map(
function ($warning) {
return sprintf('%s: %s', $warning['Code'], $warning['Message']);
},
$this->last_query_warnings
)
)
);
$message .= Toolbox::backtrace(false, 'DBmysql->query()', ['Toolbox::backtrace()']);
Toolbox::logSqlWarning($message);
ErrorHandler::getInstance()->handleSqlWarnings($this->last_query_warnings, $query);
}
if ($this->execution_time === true) {
$this->execution_time = $TIMER->getTime(0, true);
}
return $res;
}
/**
* Execute a MySQL query and die
* (optionnaly with a message) if it fails
*
* @since 0.84
*
* @param string $query Query to execute
* @param string $message Explanation of query (default '')
*
* @return mysqli_result Query result handler
*/
public function queryOrDie($query, $message = '')
{
$res = $this->query($query);
if (!$res) {
//TRANS: %1$s is the description, %2$s is the query, %3$s is the error message
$message = sprintf(
__('%1$s - Error during the database query: %2$s - Error is %3$s'),
$message,
$query,
$this->error()
);
if (isCommandLine()) {
throw new \RuntimeException($message);
} else {
echo $message . "\n";
die(1);
}
}
return $res;
}
/**
* Prepare a MySQL query
*
* @param string $query Query to prepare
*
* @return mysqli_stmt|boolean statement object or FALSE if an error occurred.
*/
public function prepare($query)
{
global $CFG_GLPI, $DEBUG_SQL, $SQL_TOTAL_REQUEST;
$res = $this->dbh->prepare($query);
if (!$res) {
// no translation for error logs
$error = " *** MySQL prepare error:\n SQL: " . $query . "\n Error: " .
$this->dbh->error . "\n";
$error .= Toolbox::backtrace(false, 'DBmysql->prepare()', ['Toolbox::backtrace()']);
Toolbox::logSqlError($error);
ErrorHandler::getInstance()->handleSqlError($this->dbh->errno, $this->dbh->error, $query);
if (
isset($_SESSION['glpi_use_mode'])
&& $_SESSION['glpi_use_mode'] == Session::DEBUG_MODE
&& $CFG_GLPI["debug_sql"]
) {
$SQL_TOTAL_REQUEST++;
$DEBUG_SQL["errors"][$SQL_TOTAL_REQUEST] = $this->error();
}
}
return $res;
}
/**
* Give result from a sql result
*
* @param mysqli_result $result MySQL result handler
* @param int $i Row offset to give
* @param string $field Field to give
*
* @return mixed Value of the Row $i and the Field $field of the Mysql $result
*/
public function result($result, $i, $field)
{
if (
$result && ($result->data_seek($i))
&& ($data = $result->fetch_array())
&& isset($data[$field])
) {
return $data[$field];
}
return null;
}
/**
* Number of rows
*
* @param mysqli_result $result MySQL result handler
*
* @return integer number of rows
*/
public function numrows($result)
{
return $result->num_rows;
}
/**
* Fetch array of the next row of a Mysql query
* Please prefer fetchRow or fetchAssoc
*
* @param mysqli_result $result MySQL result handler
*
* @return string[]|null array results
*/
public function fetchArray($result)
{
return $result->fetch_array();
}
/**
* Fetch row of the next row of a Mysql query
*
* @param mysqli_result $result MySQL result handler
*
* @return mixed|null result row
*/
public function fetchRow($result)
{
return $result->fetch_row();
}
/**
* Fetch assoc of the next row of a Mysql query
*
* @param mysqli_result $result MySQL result handler
*
* @return string[]|null result associative array
*/
public function fetchAssoc($result)
{
return $result->fetch_assoc();
}
/**
* Fetch object of the next row of an SQL query
*
* @param mysqli_result $result MySQL result handler
*
* @return object|null
*/
public function fetchObject($result)
{
return $result->fetch_object();
}
/**
* Move current pointer of a Mysql result to the specific row
*
* @param mysqli_result $result MySQL result handler
* @param integer $num Row to move current pointer
*
* @return boolean
*/
public function dataSeek($result, $num)
{
return $result->data_seek($num);
}
/**
* Give ID of the last inserted item by Mysql
*
* @return mixed
*/
public function insertId()
{
return $this->dbh->insert_id;
}
/**
* Give number of fields of a Mysql result
*
* @param mysqli_result $result MySQL result handler
*
* @return int number of fields
*/
public function numFields($result)
{
return $result->field_count;
}
/**
* Give name of a field of a Mysql result
*
* @param mysqli_result $result MySQL result handler
* @param integer $nb ID of the field
*
* @return string name of the field
*/
public function fieldName($result, $nb)
{
$finfo = $result->fetch_fields();
return $finfo[$nb]->name;
}
/**
* List tables in database
*
* @param string $table Table name condition (glpi_% as default to retrieve only glpi tables)
* @param array $where Where clause to append
*
* @return DBmysqlIterator
*/
public function listTables($table = 'glpi\_%', array $where = [])
{
$iterator = $this->request([
'SELECT' => 'table_name as TABLE_NAME',
'FROM' => 'information_schema.tables',
'WHERE' => [
'table_schema' => $this->dbdefault,
'table_type' => 'BASE TABLE',
'table_name' => ['LIKE', $table]
] + $where
]);
return $iterator;
}
/**
* Returns tables using "MyIsam" engine.
*
* @param bool $exclude_plugins
*
* @return DBmysqlIterator
*/
public function getMyIsamTables(bool $exclude_plugins = false): DBmysqlIterator
{
$criteria = [
'engine' => 'MyIsam',
];
if ($exclude_plugins) {
$criteria[] = ['NOT' => ['information_schema.tables.table_name' => ['LIKE', 'glpi\_plugin\_%']]];
}
$iterator = $this->listTables('glpi\_%', $criteria);
return $iterator;
}
/**
* Returns tables not using "utf8mb4_unicode_ci" collation.
*
* @param bool $exclude_plugins
*
* @return DBmysqlIterator
*/
public function getNonUtf8mb4Tables(bool $exclude_plugins = false): DBmysqlIterator
{
// Find tables that does not use utf8mb4 collation
$tables_query = [
'SELECT' => ['information_schema.tables.table_name as TABLE_NAME'],
'DISTINCT' => true,
'FROM' => 'information_schema.tables',
'WHERE' => [
'information_schema.tables.table_schema' => $this->dbdefault,
'information_schema.tables.table_name' => ['LIKE', 'glpi\_%'],
'information_schema.tables.table_type' => 'BASE TABLE',
['NOT' => ['information_schema.tables.table_collation' => 'utf8mb4_unicode_ci']],
],
];
// Find columns that does not use utf8mb4 collation
$columns_query = [
'SELECT' => ['information_schema.columns.table_name as TABLE_NAME'],
'DISTINCT' => true,
'FROM' => 'information_schema.columns',
'INNER JOIN' => [
'information_schema.tables' => [
'FKEY' => [
'information_schema.tables' => 'table_name',
'information_schema.columns' => 'table_name',
[
'AND' => [
'information_schema.tables.table_schema' => new QueryExpression(
$this->quoteName('information_schema.columns.table_schema')
),
]
],
]
]
],
'WHERE' => [
'information_schema.tables.table_schema' => $this->dbdefault,
'information_schema.tables.table_name' => ['LIKE', 'glpi\_%'],
'information_schema.tables.table_type' => 'BASE TABLE',
['NOT' => ['information_schema.columns.collation_name' => null]],
['NOT' => ['information_schema.columns.collation_name' => 'utf8mb4_unicode_ci']]
],
];
if ($exclude_plugins) {
$tables_query['WHERE'][] = ['NOT' => ['information_schema.tables.table_name' => ['LIKE', 'glpi\_plugin\_%']]];
$columns_query['WHERE'][] = ['NOT' => ['information_schema.tables.table_name' => ['LIKE', 'glpi\_plugin\_%']]];
}
$iterator = $this->request([
'SELECT' => ['TABLE_NAME'],
'DISTINCT' => true,
'FROM' => new QueryUnion([$tables_query, $columns_query], true),
'ORDER' => ['TABLE_NAME']
]);
return $iterator;
}
/**
* Returns tables not compatible with timezone usage, i.e. having "datetime" columns.
*
* @param bool $exclude_plugins
*
* @return DBmysqlIterator
*
* @since 10.0.0
*/
public function getTzIncompatibleTables(bool $exclude_plugins = false): DBmysqlIterator
{
$query = [
'SELECT' => ['information_schema.columns.table_name as TABLE_NAME'],
'DISTINCT' => true,
'FROM' => 'information_schema.columns',
'INNER JOIN' => [
'information_schema.tables' => [
'FKEY' => [
'information_schema.tables' => 'table_name',
'information_schema.columns' => 'table_name',
[
'AND' => [
'information_schema.tables.table_schema' => new QueryExpression(
$this->quoteName('information_schema.columns.table_schema')
),
]
],
]
]
],
'WHERE' => [
'information_schema.tables.table_schema' => $this->dbdefault,
'information_schema.tables.table_name' => ['LIKE', 'glpi\_%'],
'information_schema.tables.table_type' => 'BASE TABLE',
'information_schema.columns.data_type' => 'datetime',
],
'ORDER' => ['TABLE_NAME']
];
if ($exclude_plugins) {
$query['WHERE'][] = ['NOT' => ['information_schema.tables.table_name' => ['LIKE', 'glpi\_plugin\_%']]];
}
$iterator = $this->request($query);
return $iterator;
}
/**
* Returns columns that uses signed integers for primary/foreign keys.
*
* @param bool $exclude_plugins
*
* @return DBmysqlIterator
*
* @since 9.5.7
*/
public function getSignedKeysColumns(bool $exclude_plugins = false)
{
$query = [
'SELECT' => [
'information_schema.columns.table_name as TABLE_NAME',
'information_schema.columns.column_name as COLUMN_NAME',
'information_schema.columns.data_type as DATA_TYPE',
'information_schema.columns.column_default as COLUMN_DEFAULT',
'information_schema.columns.is_nullable as IS_NULLABLE',
'information_schema.columns.extra as EXTRA',
],
'FROM' => 'information_schema.columns',
'INNER JOIN' => [
'information_schema.tables' => [
'FKEY' => [
'information_schema.tables' => 'table_name',
'information_schema.columns' => 'table_name',
[
'AND' => [
'information_schema.tables.table_schema' => new QueryExpression(
$this->quoteName('information_schema.columns.table_schema')
),
]
],
]
]
],
'WHERE' => [
'information_schema.tables.table_schema' => $this->dbdefault,
'information_schema.tables.table_name' => ['LIKE', 'glpi\_%'],
'information_schema.tables.table_type' => 'BASE TABLE',
[
'OR' => [
['information_schema.columns.column_name' => 'id'],
['information_schema.columns.column_name' => ['LIKE', '%\_id']],
['information_schema.columns.column_name' => ['LIKE', '%\_id\_%']],
],
],
'information_schema.columns.data_type' => ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'],
['NOT' => ['information_schema.columns.column_type' => ['LIKE', '%unsigned%']]],
],
'ORDER' => ['TABLE_NAME']
];
if ($exclude_plugins) {
$query['WHERE'][] = ['NOT' => ['information_schema.tables.table_name' => ['LIKE', 'glpi\_plugin\_%']]];
}
$iterator = $this->request($query);
return $iterator;
}
/**
* Returns foreign keys constraints.
*
* @return DBmysqlIterator
*
* @since 9.5.7
*/
public function getForeignKeysContraints()
{
$query = [
'SELECT' => [
'table_schema as TABLE_SCHEMA',
'table_name as TABLE_NAME',
'column_name as COLUMN_NAME',
'constraint_name as CONSTRAINT_NAME',
'referenced_table_name as REFERENCED_TABLE_NAME',
'referenced_column_name as REFERENCED_COLUMN_NAME',
'ordinal_position as ORDINAL_POSITION',
],
'FROM' => 'information_schema.key_column_usage',
'WHERE' => [
'referenced_table_schema' => $this->dbdefault,
'referenced_table_name' => ['LIKE', 'glpi\_%'],
],
'ORDER' => ['TABLE_NAME']
];
$iterator = $this->request($query);
return $iterator;
}
/**
* List fields of a table
*
* @param string $table Table name condition
* @param boolean $usecache If use field list cache (default true)
*
* @return mixed list of fields
*/
public function listFields($table, $usecache = true)
{
if (!$this->cache_disabled && $usecache && isset($this->field_cache[$table])) {
return $this->field_cache[$table];
}
$result = $this->query("SHOW COLUMNS FROM `$table`");
if ($result) {
if ($this->numrows($result) > 0) {
$this->field_cache[$table] = [];
while ($data = $this->fetchAssoc($result)) {
$this->field_cache[$table][$data["Field"]] = $data;
}
return $this->field_cache[$table];
}
return [];
}
return false;
}
/**
* Get field of a table
*
* @param string $table
* @param string $field
* @param boolean $usecache
*
* @return array|null Field characteristics
*/
public function getField(string $table, string $field, $usecache = true): ?array
{
$fields = $this->listFields($table, $usecache);
return $fields[$field] ?? null;
}
/**
* Get number of affected rows in previous MySQL operation
*
* @return int number of affected rows on success, and -1 if the last query failed.
*/
public function affectedRows()
{
return $this->dbh->affected_rows;
}
/**
* Free result memory
*
* @param mysqli_result $result MySQL result handler
*
* @return boolean
*/
public function freeResult($result)
{
return $result->free();
}
/**
* Returns the numerical value of the error message from previous MySQL operation
*
* @return int error number from the last MySQL function, or 0 (zero) if no error occurred.
*/
public function errno()
{
return $this->dbh->errno;
}
/**
* Returns the text of the error message from previous MySQL operation
*
* @return string error text from the last MySQL function, or '' (empty string) if no error occurred.
*/
public function error()
{
return $this->dbh->error;
}
/**
* Close MySQL connection
*
* @return boolean TRUE on success or FALSE on failure.
*/
public function close()
{
if ($this->connected && $this->dbh) {
return $this->dbh->close();
}
return false;
}
/**
* is a slave database ?
*
* @return boolean
*/
public function isSlave()
{
return $this->slave;
}
/**
* Execute all the request in a file
*
* @param string $path with file full path
*
* @return boolean true if all query are successfull
*/
public function runFile($path)
{
$script = fopen($path, 'r');
if (!$script) {
return false;
}
$sql_query = @fread(
$script,
@filesize($path)
) . "\n";
$sql_query = html_entity_decode($sql_query, ENT_COMPAT, 'UTF-8');
$sql_query = $this->removeSqlRemarks($sql_query);
$queries = preg_split('/;\s*$/m', $sql_query);
foreach ($queries as $query) {
$query = trim($query);
if ($query != '') {
$query = htmlentities($query, ENT_COMPAT, 'UTF-8');
if (!$this->query($query)) {
return false;
}
if (!isCommandLine()) {
// Flush will prevent proxy to timeout as it will receive data.
// Flush requires a content to be sent, so we sent spaces as multiple spaces
// will be shown as a single one on browser.
echo ' ';
Html::glpi_flush();
}
}
}
return true;
}
/**
* Instanciate a Simple DBIterator
*
* Examples =
* foreach ($DB->request("select * from glpi_states") as $data) { ... }
* foreach ($DB->request("glpi_states") as $ID => $data) { ... }
* foreach ($DB->request("glpi_states", "ID=1") as $ID => $data) { ... }
* foreach ($DB->request("glpi_states", "", "name") as $ID => $data) { ... }
* foreach ($DB->request("glpi_computers",array("name"=>"SBEI003W","entities_id"=>1),array("serial","otherserial")) { ... }
*
* Examples =
* array("id"=>NULL)
* array("OR"=>array("id"=>1, "NOT"=>array("state"=>3)));
* array("AND"=>array("id"=>1, array("NOT"=>array("state"=>array(3,4,5),"toto"=>2))))
*
* FIELDS name or array of field names
* ORDER name or array of field names
* LIMIT max of row to retrieve
* START first row to retrieve
*
* @param string|string[] $tableorsql Table name, array of names or SQL query
* @param string|string[] $crit String or array of filed/values, ex array("id"=>1), if empty => all rows
* (default '')
* @param boolean $debug To log the request (default false)
*
* @return DBmysqlIterator
*/
public function request($tableorsql, $crit = "", $debug = false)
{
$iterator = new DBmysqlIterator($this);
$iterator->execute($tableorsql, $crit, $debug);
return $iterator;
}
/**
* Get information about DB connection for showSystemInformation
*
* @since 0.84
*
* @return string[] Array of label / value
*/
public function getInfo()
{
// No translation, used in sysinfo
$ret = [];
$req = $this->request("SELECT @@sql_mode as mode, @@version AS vers, @@version_comment AS stype");
if (($data = $req->current())) {
if ($data['stype']) {
$ret['Server Software'] = $data['stype'];
}
if ($data['vers']) {
$ret['Server Version'] = $data['vers'];
} else {
$ret['Server Version'] = $this->dbh->server_info;
}
if ($data['mode']) {
$ret['Server SQL Mode'] = $data['mode'];
} else {
$ret['Server SQL Mode'] = '';
}
}
$ret['Parameters'] = $this->dbuser . "@" . $this->dbhost . "/" . $this->dbdefault;
$ret['Host info'] = $this->dbh->host_info;
return $ret;
}
/**
* Get a global DB lock
*
* @since 0.84
*
* @param string $name lock's name
*
* @return boolean
*/
public function getLock($name)
{
$name = addslashes($this->dbdefault . '.' . $name);
$query = "SELECT GET_LOCK('$name', 0)";
$result = $this->query($query);
list($lock_ok) = $this->fetchRow($result);
return (bool)$lock_ok;
}
/**
* Release a global DB lock
*
* @since 0.84
*
* @param string $name lock's name
*
* @return boolean
*/
public function releaseLock($name)
{
$name = addslashes($this->dbdefault . '.' . $name);
$query = "SELECT RELEASE_LOCK('$name')";
$result = $this->query($query);
list($lock_ok) = $this->fetchRow($result);
return $lock_ok;
}
/**
* Check if a table exists
*
* @since 9.2
* @since 9.5 Added $usecache parameter.
*
* @param string $tablename Table name
* @param boolean $usecache If use table list cache
*
* @return boolean
**/
public function tableExists($tablename, $usecache = true)
{
if (!$this->cache_disabled && $usecache && in_array($tablename, $this->table_cache)) {
return true;
}
// Retrieve all tables if cache is empty but enabled, in order to fill cache
// with all known tables
$retrieve_all = !$this->cache_disabled && empty($this->table_cache);
$result = $this->listTables($retrieve_all ? 'glpi\_%' : $tablename);
$found_tables = [];
foreach ($result as $data) {
$found_tables[] = $data['TABLE_NAME'];
}
if (!$this->cache_disabled) {
$this->table_cache = array_unique(array_merge($this->table_cache, $found_tables));
}
if (in_array($tablename, $found_tables)) {
return true;
}
return false;
}
/**
* Check if a field exists
*
* @since 9.2
*
* @param string $table Table name for the field we're looking for
* @param string $field Field name
* @param Boolean $usecache Use cache; @see DBmysql::listFields(), defaults to true
*
* @return boolean
**/
public function fieldExists($table, $field, $usecache = true)
{
if (!$this->tableExists($table, $usecache)) {
trigger_error("Table $table does not exists", E_USER_WARNING);
return false;
}
if ($fields = $this->listFields($table, $usecache)) {
if (isset($fields[$field])) {
return true;
}
return false;
}
return false;
}
/**
* Disable table cache globally; usefull for migrations
*
* @return void
*/
public function disableTableCaching()
{
$this->cache_disabled = true;
}
/**
* Quote field name
*
* @since 9.3
*
* @param string $name of field to quote (or table.field)
*
* @return string
*/
public static function quoteName($name)
{
//handle verbatim names
if ($name instanceof QueryExpression) {
return $name->getValue();
}
//handle aliases
$names = preg_split('/\s+AS\s+/i', $name);
if (count($names) > 2) {
throw new \RuntimeException(
'Invalid field name ' . $name
);
}
if (count($names) == 2) {
$name = self::quoteName($names[0]);
$name .= ' AS ' . self::quoteName($names[1]);
return $name;
} else {
if (strpos($name, '.')) {
$n = explode('.', $name, 2);
$table = self::quoteName($n[0]);
$field = ($n[1] === '*') ? $n[1] : self::quoteName($n[1]);
return "$table.$field";
}
return ($name[0] == '`' ? $name : ($name === '*' ? $name : "`$name`"));
}
}
/**
* Quote value for insert/update
*
* @param mixed $value Value
*
* @return mixed
*/
public static function quoteValue($value)
{
if ($value instanceof QueryParam || $value instanceof QueryExpression) {
//no quote for query parameters nor expressions
$value = $value->getValue();
} else if ($value === null || $value === 'NULL' || $value === 'null') {
$value = 'NULL';
} else if (is_bool($value)) {
// transform boolean as int (prevent `false` to be transformed to empty string)
$value = "'" . (int)$value . "'";
} else {
if (Sanitizer::isNsClassOrCallableIdentifier($value)) {
// Values that corresponds to an existing namespaced class are not sanitized (see `Glpi\Toolbox\Sanitizer::sanitize()`).
// However, they have to be escaped in SQL queries.
// Note: method is called statically, so `$DB` may be not defined yet in edge cases (install process).
global $DB;
$value = $DB instanceof DBmysql && $DB->connected ? $DB->escape($value) : $value;
}
// phone numbers may start with '+' and will be considered as numeric
$value = "'$value'";
}
return $value;
}
/**
* Builds an insert statement
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([field name => field value)
*
* @return string
*/
public function buildInsert($table, $params)
{
$query = "INSERT INTO " . self::quoteName($table) . " (";
$fields = [];
foreach ($params as $key => &$value) {
$fields[] = $this->quoteName($key);
$value = $this->quoteValue($value);
}
$query .= implode(', ', $fields);
$query .= ") VALUES (";
$query .= implode(", ", $params);
$query .= ")";
return $query;
}
/**
* Insert a row in the database
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([field name => field value)
*
* @return mysqli_result|boolean Query result handler
*/
public function insert($table, $params)
{
$result = $this->query(
$this->buildInsert($table, $params)
);
return $result;
}
/**
* Insert a row in the database and die
* (optionnaly with a message) if it fails
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([field name => field value)
* @param string $message Explanation of query (default '')
*
* @return mysqli_result|boolean Query result handler
*/
public function insertOrDie($table, $params, $message = '')
{
$insert = $this->buildInsert($table, $params);
$res = $this->query($insert);
if (!$res) {
//TRANS: %1$s is the description, %2$s is the query, %3$s is the error message
$message = sprintf(
__('%1$s - Error during the database query: %2$s - Error is %3$s'),
$message,
$insert,
$this->error()
);
if (isCommandLine()) {
throw new \RuntimeException($message);
} else {
echo $message . "\n";
die(1);
}
}
return $res;
}
/**
* Builds an update statement
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([field name => field value)
* @param array $clauses Clauses to use. If not 'WHERE' key specified, will b the WHERE clause (@see DBmysqlIterator capabilities)
* @param array $joins JOINS criteria array
*
* @since 9.4.0 $joins parameter added
* @return string
*/
public function buildUpdate($table, $params, $clauses, array $joins = [])
{
//when no explicit "WHERE", we only have a WHEre clause.
if (!isset($clauses['WHERE'])) {
$clauses = ['WHERE' => $clauses];
} else {
$known_clauses = ['WHERE', 'ORDER', 'LIMIT', 'START'];
foreach (array_keys($clauses) as $key) {
if (!in_array($key, $known_clauses)) {
throw new \RuntimeException(
str_replace(
'%clause',
$key,
'Trying to use an unknonw clause (%clause) building update query!'
)
);
}
}
}
if (!count($clauses['WHERE'])) {
throw new \RuntimeException('Cannot run an UPDATE query without WHERE clause!');
}
$query = "UPDATE " . self::quoteName($table);
//JOINS
$it = new DBmysqlIterator($this);
$query .= $it->analyseJoins($joins);
$query .= " SET ";
foreach ($params as $field => $value) {
$query .= self::quoteName($field) . " = " . $this->quoteValue($value) . ", ";
}
$query = rtrim($query, ', ');
$query .= " WHERE " . $it->analyseCrit($clauses['WHERE']);
// ORDER BY
if (isset($clauses['ORDER']) && !empty($clauses['ORDER'])) {
$query .= $it->handleOrderClause($clauses['ORDER']);
}
if (isset($clauses['LIMIT']) && !empty($clauses['LIMIT'])) {
$offset = (isset($clauses['START']) && !empty($clauses['START'])) ? $clauses['START'] : null;
$query .= $it->handleLimits($clauses['LIMIT'], $offset);
}
return $query;
}
/**
* Update a row in the database
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([:field name => field value)
* @param array $where WHERE clause
* @param array $joins JOINS criteria array
*
* @since 9.4.0 $joins parameter added
* @return mysqli_result|boolean Query result handler
*/
public function update($table, $params, $where, array $joins = [])
{
$query = $this->buildUpdate($table, $params, $where, $joins);
$result = $this->query($query);
return $result;
}
/**
* Update a row in the database or die
* (optionnaly with a message) if it fails
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([:field name => field value)
* @param array $where WHERE clause
* @param string $message Explanation of query (default '')
* @param array $joins JOINS criteria array
*
* @since 9.4.0 $joins parameter added
* @return mysqli_result|boolean Query result handler
*/
public function updateOrDie($table, $params, $where, $message = '', array $joins = [])
{
$update = $this->buildUpdate($table, $params, $where, $joins);
$res = $this->query($update);
if (!$res) {
//TRANS: %1$s is the description, %2$s is the query, %3$s is the error message
$message = sprintf(
__('%1$s - Error during the database query: %2$s - Error is %3$s'),
$message,
$update,
$this->error()
);
if (isCommandLine()) {
throw new \RuntimeException($message);
} else {
echo $message . "\n";
die(1);
}
}
return $res;
}
/**
* Update a row in the database or insert a new one
*
* @since 9.4
*
* @param string $table Table name
* @param array $params Query parameters ([:field name => field value)
* @param array $where WHERE clause
* @param boolean $onlyone Do the update only one one element, defaults to true
*
* @return mysqli_result|boolean Query result handler
*/
public function updateOrInsert($table, $params, $where, $onlyone = true)
{
$req = $this->request($table, $where);
$data = array_merge($where, $params);
if ($req->count() == 0) {
return $this->insertOrDie($table, $data, 'Unable to create new element or update existing one');
} else if ($req->count() == 1 || !$onlyone) {
return $this->updateOrDie($table, $data, $where, 'Unable to create new element or update existing one');
} else {
trigger_error('Update would change too many rows!', E_USER_WARNING);
return false;
}
}
/**
* Builds a delete statement
*
* @since 9.3
*
* @param string $table Table name
* @param array $params Query parameters ([field name => field value)
* @param array $where WHERE clause (@see DBmysqlIterator capabilities)
* @param array $joins JOINS criteria array
*
* @since 9.4.0 $joins parameter added
* @return string
*/
public function buildDelete($table, $where, array $joins = [])
{
if (!count($where)) {
throw new \RuntimeException('Cannot run an DELETE query without WHERE clause!');
}
$query = "DELETE " . self::quoteName($table) . " FROM " . self::quoteName($table);
$it = new DBmysqlIterator($this);
$query .= $it->analyseJoins($joins);
$query .= " WHERE " . $it->analyseCrit($where);
return $query;
}
/**
* Delete rows in the database
*
* @since 9.3
*
* @param string $table Table name
* @param array $where WHERE clause
* @param array $joins JOINS criteria array
*
* @since 9.4.0 $joins parameter added
* @return mysqli_result|boolean Query result handler
*/
public function delete($table, $where, array $joins = [])
{
$query = $this->buildDelete($table, $where, $joins);
$result = $this->query($query);
return $result;
}
/**
* Delete a row in the database and die
* (optionnaly with a message) if it fails
*
* @since 9.3
*
* @param string $table Table name
* @param array $where WHERE clause
* @param string $message Explanation of query (default '')
* @param array $joins JOINS criteria array
*
* @since 9.4.0 $joins parameter added
* @return mysqli_result|boolean Query result handler
*/
public function deleteOrDie($table, $where, $message = '', array $joins = [])
{
$update = $this->buildDelete($table, $where, $joins);
$res = $this->query($update);
if (!$res) {
//TRANS: %1$s is the description, %2$s is the query, %3$s is the error message
$message = sprintf(
__('%1$s - Error during the database query: %2$s - Error is %3$s'),
$message,
$update,
$this->error()
);
if (isCommandLine()) {
throw new \RuntimeException($message);
} else {
echo $message . "\n";
die(1);
}
}
return $res;
}
/**
* Truncate table in the database
*
* @since 10.0.0
*
* @param string $table Table name
*
* @return mysqli_result|boolean Query result handler
*/
public function truncate($table)
{
// Use delete to prevent table corruption on some MySQL operations
// (i.e. when using mysqldump without `--single-transaction` option)
return $this->delete($table, [1]);
}
/**
* Truncate table in the database or die
* (optionally with a message) if it fails
*
* @since 10.0.0
*
* @param string $table Table name
* @param string $message Explanation of query (default '')
*
* @return mysqli_result|boolean Query result handler
*/
public function truncateOrDie($table, $message = '')
{
$table_name = $this::quoteName($table);
$res = $this->query("TRUNCATE $table_name");
if (!$res) {
//TRANS: %1$s is the description, %2$s is the query, %3$s is the error message
$message = sprintf(
__('%1$s - Error during the database query: %2$s - Error is %3$s'),
$message,
"TRUNCATE $table",
$this->error()
);
if (isCommandLine()) {
throw new \RuntimeException($message);
}
echo $message . "\n";
die(1);
}
return $res;
}
/**
* Get database raw version
*
* @return string
*/
public function getVersion()
{
$req = $this->request('SELECT version()')->current();
$raw = $req['version()'];
return $raw;
}
/**
* Starts a transaction
*
* @return boolean
*/
public function beginTransaction()
{
if ($this->in_transaction === true) {
trigger_error('A database transaction has already been started!', E_USER_WARNING);
}
$this->in_transaction = true;
return $this->dbh->begin_transaction();
}
public function setSavepoint(string $name, $force = false)
{
if (!$this->in_transaction && $force) {
$this->beginTransaction();
}
if ($this->in_transaction) {
$this->dbh->savepoint($name);
} else {
// Not already in transaction or failed to start one now
trigger_error('Unable to set DB savepoint because no transaction was started', E_USER_WARNING);
}
}
/**
* Commits a transaction
*
* @return boolean
*/
public function commit()
{
$this->in_transaction = false;
return $this->dbh->commit();
}
/**
* Rollbacks a transaction completely or to a specified savepoint
*
* @return boolean
*/
public function rollBack($savepoint = null)
{
if (!$savepoint) {
$this->in_transaction = false;
$this->dbh->rollback();
} else {
$this->rollbackTo($savepoint);
}
}
protected function rollbackTo($name)
{
// No proper rollback to savepoint support in mysqli extension?
$this->query('ROLLBACK TO ' . self::quoteName($name));
}
/**
* Are we in a transaction?
*
* @return boolean
*/
public function inTransaction()
{
return $this->in_transaction;
}
/**
* Defines timezone to use.
*
* @param string $timezone
*
* @return DBmysql
*/
public function setTimezone($timezone)
{
//setup timezone
if ($this->use_timezones) {
date_default_timezone_set($timezone);
$this->dbh->query("SET SESSION time_zone = '$timezone'");
$_SESSION['glpi_currenttime'] = date("Y-m-d H:i:s");
}
return $this;
}
/**
* Returns list of timezones.
*
* @return string[]
*
* @since 9.5.0
*/
public function getTimezones()
{
if (!$this->use_timezones) {
return [];
}
$list = []; //default $tz is empty
$from_php = \DateTimeZone::listIdentifiers();
$now = new \DateTime();
$iterator = $this->request([
'SELECT' => 'Name',
'FROM' => 'mysql.time_zone_name',
'WHERE' => ['Name' => $from_php]
]);
foreach ($iterator as $from_mysql) {
$now->setTimezone(new \DateTimeZone($from_mysql['Name']));
$list[$from_mysql['Name']] = $from_mysql['Name'] . $now->format(" (T P)");
}
return $list;
}
/**
* Clear cached schema information.
*
* @return void
*/
public function clearSchemaCache()
{
$this->table_cache = [];
$this->field_cache = [];
}
/**
* Quote a value for a specified type
* Should be used for PDO, but this will prevent heavy
* replacements in the source code in the future.
*
* @param mixed $value Value to quote
* @param integer $type Value type, defaults to PDO::PARAM_STR
*
* @return mixed
*
* @since 9.5.0
*/
public function quote($value, int $type = 2/*\PDO::PARAM_STR*/)
{
return "'" . $this->escape($value) . "'";
//return $this->dbh->quote($value, $type);
}
/**
* Get character used to quote names for current database engine
*
* @return string
*
* @since 9.5.0
*/
public static function getQuoteNameChar(): string
{
return '`';
}
/**
* Is value quoted as database field/expression?
*
* @param string|\QueryExpression $value Value to check
*
* @return boolean
*
* @since 9.5.0
*/
public static function isNameQuoted($value): bool
{
$quote = static::getQuoteNameChar();
return is_string($value) && trim($value, $quote) != $value;
}
/**
* Remove SQL comments
* © 2011 PHPBB Group
*
* @param string $output SQL statements
*
* @return string
*/
public function removeSqlComments($output)
{
$lines = explode("\n", $output);
$output = "";
// try to keep mem. use down
$linecount = count($lines);
$in_comment = false;
for ($i = 0; $i < $linecount; $i++) {
if (preg_match("/^\/\*/", $lines[$i])) {
$in_comment = true;
}
if (!$in_comment) {
$output .= $lines[$i] . "\n";
}
if (preg_match("/\*\/$/", preg_quote($lines[$i]))) {
$in_comment = false;
}
}
unset($lines);
return trim($output);
}
/**
* Remove remarks and comments from SQL
* @see DBmysql::removeSqlComments()
* © 2011 PHPBB Group
*
* @param $string $sql SQL statements
*
* @return string
*/
public function removeSqlRemarks($sql)
{
$lines = explode("\n", $sql);
// try to keep mem. use down
$sql = "";
$linecount = count($lines);
$output = "";
for ($i = 0; $i < $linecount; $i++) {
if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0)) {
if (isset($lines[$i][0])) {
if ($lines[$i][0] != "#" && substr($lines[$i], 0, 2) != "--") {
$output .= $lines[$i] . "\n";
} else {
$output .= "\n";
}
}
// Trading a bit of speed for lower mem. use here.
$lines[$i] = "";
}
}
return trim($this->removeSqlComments($output));
}
/**
* Fetch warnings from last query.
*
* @return array
*/
private function fetchQueryWarnings(): array
{
$warnings = [];
if ($this->dbh->warning_count > 0 && $warnings_result = $this->dbh->query('SHOW WARNINGS')) {
// Warnings to exclude
$excludes = [];
if (!$this->use_utf8mb4 || !$this->log_deprecation_warnings) {
// Exclude warnings related to usage of "utf8mb3" charset, as database has not been migrated yet.
$excludes[] = 1287; // 'utf8mb3' is deprecated and will be removed in a future release. Please use utf8mb4 instead.
$excludes[] = 3719; // 'utf8' is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous.
$excludes[] = 3778; // 'utf8_unicode_ci' is a collation of the deprecated character set UTF8MB3. Please consider using UTF8MB4 with an appropriate collation instead.
}
if (!$this->log_deprecation_warnings) {
// Mute deprecations related to elements that are heavilly used in old migrations and in plugins
// as it may require a lot of work to fix them.
$excludes[] = 1681; // Integer display width is deprecated and will be removed in a future release.
}
while ($warning = $warnings_result->fetch_assoc()) {
if ($warning['Level'] === 'Note' || in_array($warning['Code'], $excludes)) {
continue;
}
$warnings[] = $warning;
}
}
return $warnings;
}
/**
* Get SQL warnings related to last query.
* @return array
*/
public function getLastQueryWarnings(): array
{
return $this->last_query_warnings;
}
/**
* Set charset to use for DB connection.
*
* @return void
*/
public function setConnectionCharset(): void
{
DBConnection::setConnectionCharset($this->dbh, $this->use_utf8mb4);
}
/**
* Executes a prepared statement
*
* @param mysqli_stmt $stmt STatement to execute
*
* @return void
*/
public function executeStatement(mysqli_stmt $stmt): void
{
if (!$stmt->execute()) {
trigger_error($stmt->error, E_USER_ERROR);
}
}
/**
* Check for deprecated table options during ALTER/CREATE TABLE queries.
*
* @param string $query
*
* @return void
*/
private function checkForDeprecatedTableOptions(string $query): void
{
$table_matches = [];
$table_pattern = '/'
. '(ALTER|CREATE)'
. '(\s+TEMPORARY)?'
. '\s+TABLE'
. '(\s+IF\s+NOT\s+EXISTS)?'
. '\s*(\s|`)(?<table>[^`\s]+)(\s|`)'
. '/i';
if (preg_match($table_pattern, $query, $table_matches) !== 1) {
return;
}
// Wrong UTF8 charset/collation
$charset_matches = [];
if ($this->use_utf8mb4 && preg_match('/(?<invalid>(utf8(_[^\';\s]+)?))([\';\s]|$)/', $query, $charset_matches)) {
trigger_error(
sprintf(
'Usage of "%s" charset/collation detected, should be "%s"',
$charset_matches['invalid'],
str_replace('utf8', 'utf8mb4', $charset_matches['invalid'])
),
E_USER_WARNING
);
} else if (!$this->use_utf8mb4 && preg_match('/(?<invalid>(utf8mb4(_[^\';\s]+)?))([\';\s]|$)/', $query, $charset_matches)) {
trigger_error(
sprintf(
'Usage of "%s" charset/collation detected, should be "%s"',
$charset_matches['invalid'],
str_replace('utf8mb4', 'utf8', $charset_matches['invalid'])
),
E_USER_WARNING
);
}
// Usage of MyISAM
if (!$this->allow_myisam && preg_match('/[)\s]engine\s*=\s*\'?myisam([\';\s]|$)/i', $query)) {
trigger_error('Usage of "MyISAM" engine is discouraged, please use "InnoDB" engine.', E_USER_WARNING);
}
// Usage of datetime
if (!$this->allow_datetime && preg_match('/ datetime /i', $query)) {
trigger_error('Usage of "DATETIME" fields is discouraged, please use "TIMESTAMP" fields instead.', E_USER_WARNING);
}
// Usage of signed integers in primary/foreign keys
$pattern = '/'
. '(\s|`)(?<field>id|[^`\s]+_id|[^`\s]+_id_[^`\s]+)(\s|`)' // `id`, `xxx_id` or `xxx_id_yyy` field
. '\s*'
. '(tiny|small|medium|big)?int' // with int type
. '(?!\s+unsigned)' // not unsigned
. '/i';
$field_matches = [];
if (!$this->allow_signed_keys && preg_match($pattern, $query, $field_matches)) {
trigger_error(
sprintf(
'Usage of signed integers in primary or foreign keys is discouraged, please use unsigned integers instead in `%s`.`%s`.',
$table_matches['table'],
$field_matches['field']
),
E_USER_WARNING
);
}
}
/**
* Return configuration boolean properties computed using current state of tables.
*
* @return array
*/
public function getComputedConfigBooleanFlags(): array
{
$config_flags = [];
if ($this->getTzIncompatibleTables(true)->count() === 0) {
// Disallow datetime if there is no core table still using this field type.
$config_flags[DBConnection::PROPERTY_ALLOW_DATETIME] = false;
$timezones_requirement = new DbTimezones($this);
if ($timezones_requirement->isValidated()) {
// Activate timezone usage if timezones are available and all tables are already migrated.
$config_flags[DBConnection::PROPERTY_USE_TIMEZONES] = true;
}
}
if ($this->getNonUtf8mb4Tables(true)->count() === 0) {
// Use utf8mb4 charset for update process if there all core table are using this charset.
$config_flags[DBConnection::PROPERTY_USE_UTF8MB4] = true;
}
if ($this->getMyIsamTables(true)->count() === 0) {
// Disallow MyISAM if there is no core table still using this engine.
$config_flags[DBConnection::PROPERTY_ALLOW_MYISAM] = false;
}
if ($this->getSignedKeysColumns(true)->count() === 0) {
// Disallow MyISAM if there is no core table still using this engine.
$config_flags[DBConnection::PROPERTY_ALLOW_SIGNED_KEYS] = false;
}
return $config_flags;
}
}