HEX
Server: Apache
System: Linux darrell.nocdirect.com 4.18.0-513.18.2.el8_9.x86_64 #1 SMP Sat Mar 30 06:10:41 EDT 2024 x86_64
User: joderbya (1358)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: /home/joderbya/public_html/ss-servicos/nacala/core/db/mysqli.class.php
<?php
/* Copyright (C) 2001		Fabien Seisen			<seisen@linuxfr.org>
 * Copyright (C) 2002-2005	Rodolphe Quiedeville	<rodolphe@quiedeville.org>
 * Copyright (C) 2004-2011	Laurent Destailleur		<eldy@users.sourceforge.net>
 * Copyright (C) 2006		Andre Cianfarani		<acianfa@free.fr>
 * Copyright (C) 2005-2012	Regis Houssin			<regis.houssin@inodbox.com>
 * Copyright (C) 2015       Raphaël Doursenaud      <rdoursenaud@gpcsolutions.fr>
 * Copyright (C) 2024-2025	MDW						<mdeweerd@users.noreply.github.com>
 * Copyright (C) 2024       Charlene Benke	        <charlene@patas-monkey.com>
 * Copyright (C) 2024       Frédéric France         <frederic.france@free.fr>
 *
 * 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/>.
 */

/**
 *	\file       htdocs/core/db/mysqli.class.php
 *	\brief      Class file to manage Dolibarr database access for a MySQL database
 */

require_once DOL_DOCUMENT_ROOT.'/core/db/DoliDB.class.php';

/**
 *	Class to manage Dolibarr database access for a MySQL database using the MySQLi extension
 */
class DoliDBMysqli extends DoliDB
{
	/** @var mysqli Database object */
	public $db;
	//! Database type
	public $type = 'mysqli';

	//! Database label
	const LABEL = 'MySQL or MariaDB';
	//! Version min database
	const VERSIONMIN = '5.0.3';

	/** @var bool|mysqli_result Resultset of last query */
	private $_results;

	/**
	 *	Constructor.
	 *	This create an opened connection to a database server and eventually to a database
	 *
	 *	@param      string	$type		Type of database (mysql, pgsql...). Not used.
	 *	@param	    string	$host		Address of database server
	 *	@param	    string	$user		Name of database user
	 *	@param	    string	$pass		Password of database user
	 *	@param	    string	$name		Name of database
	 *	@param	    int		$port		Port of database server
	 */
	public function __construct($type, $host, $user, $pass, $name = '', $port = 0)
	{
		global $conf, $langs;

		// Note that having "static" property for "$forcecharset" and "$forcecollate" will make error here in strict mode, so they are not static
		if (!empty($conf->db->character_set)) {
			$this->forcecharset = $conf->db->character_set;
		}
		if (!empty($conf->db->dolibarr_main_db_collation)) {
			$this->forcecollate = $conf->db->dolibarr_main_db_collation;
		}

		$this->database_user = $user;
		$this->database_host = $host;
		$this->database_port = $port;

		$this->transaction_opened = 0;

		//print "Name DB: $host,$user,$pass,$name<br>";

		if (!class_exists('mysqli')) {
			$this->connected = false;
			$this->ok = false;
			$this->error = "Mysqli PHP functions for using Mysqli driver are not available in this version of PHP. Try to use another driver.";
			dol_syslog(get_class($this)."::DoliDBMysqli : Mysqli PHP functions for using Mysqli driver are not available in this version of PHP. Try to use another driver.", LOG_ERR);
		}

		if (!$host) {
			$this->connected = false;
			$this->ok = false;
			$this->error = $langs->trans("ErrorWrongHostParameter");
			dol_syslog(get_class($this)."::DoliDBMysqli : Connect error, wrong host parameters", LOG_ERR);
		}

		// Try server connection
		// We do not try to connect to database, only to server. Connect to database is done later in constructor
		$this->db = $this->connect($host, $user, $pass, '', $port);

		if ($this->db && empty($this->db->connect_errno)) {
			$this->connected = true;
			$this->ok = true;
		} else {
			$this->connected = false;
			$this->ok = false;
			$this->error = empty($this->db) ? 'Failed to connect' : $this->db->connect_error;
			dol_syslog(get_class($this)."::DoliDBMysqli Connect error: ".$this->error, LOG_ERR);
		}

		$disableforcecharset = 0;	// Set to 1 to test without charset forcing

		// If server connection is ok, we try to connect to the database
		if ($this->connected && $name) {
			if ($this->select_db($name)) {
				$this->database_selected = true;
				$this->database_name = $name;
				$this->ok = true;

				// If client is old latin, we force utf8
				$clientmustbe = empty($conf->db->character_set) ? 'utf8' : (string) $conf->db->character_set;
				if (preg_match('/latin1/', $clientmustbe)) {
					$clientmustbe = 'utf8';
				}

				if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
					try {
						dol_syslog(get_class($this)."::DoliDBMysqli You should set the \$dolibarr_main_db_character_set and \$dolibarr_main_db_collation for the PHP to the same as the database default, so to ".$this->db->character_set_name(). " or upgrade database default to ".$clientmustbe.".", LOG_WARNING);
						// To get current charset:   USE databasename; SHOW VARIABLES LIKE 'character_set_database'
						//                     or:   USE databasename; SELECT schema_name, default_character_set_name FROM information_schema.SCHEMATA;
						// To get current collation: USE databasename; SHOW VARIABLES LIKE 'collation_database'
						//                       or: USE databasename; SELECT schema_name, default_character_set_name FROM information_schema.SCHEMATA;
						// To upgrade database default, you can do: ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

						$this->db->set_charset($clientmustbe); // This set charset, but with a bad collation (colllation is forced later)
					} catch (Exception $e) {
						print 'Failed to force character_set_client to '.$clientmustbe." (according to setup) to match the one of the server database.<br>\n";
						print $e->getMessage();
						print "<br>\n";
						if ($clientmustbe != 'utf8') {
							print 'Edit conf/conf.php file to set a charset "utf8"';
							if ($clientmustbe != 'utf8mb4') {
								print ' or "utf8mb4"';
							}
							print ' instead of "'.$clientmustbe.'".'."\n";
						}
						exit;
					}

					$collation = (empty($conf) ? 'utf8_unicode_ci' : $conf->db->dolibarr_main_db_collation);
					if (preg_match('/latin1/', $collation)) {
						$collation = 'utf8_unicode_ci';
					}

					if (!preg_match('/general/', $collation)) {
						$this->db->query("SET collation_connection = ".$collation);
					}
				}
			} else {
				$this->database_selected = false;
				$this->database_name = '';
				$this->ok = false;
				$this->error = $this->error();
				dol_syslog(get_class($this)."::DoliDBMysqli : Select_db error ".$this->error, LOG_ERR);
			}
		} else {
			// No selection of database done. We may only be connected or not (ok or ko) to the server.
			$this->database_selected = false;

			if ($this->connected) {
				// If client is old latin, we force utf8
				$clientmustbe = empty($conf->db->character_set) ? 'utf8' : (string) $conf->db->character_set;
				if (preg_match('/latin1/', $clientmustbe)) {
					$clientmustbe = 'utf8';
				}
				if (preg_match('/utf8mb4/', $clientmustbe)) {
					$clientmustbe = 'utf8';
				}

				if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
					$this->db->set_charset($clientmustbe); // This set utf8_unicode_ci

					$collation = $conf->db->dolibarr_main_db_collation;
					if (preg_match('/latin1/', $collation)) {
						$collation = 'utf8_unicode_ci';
					}
					if (preg_match('/utf8mb4/', $collation)) {
						$collation = 'utf8_unicode_ci';
					}

					if (!preg_match('/general/', $collation)) {
						$this->db->query("SET collation_connection = ".$collation);
					}
				}
			}
		}
	}


	/**
	 * Return SQL string to force an index
	 *
	 * @param	string	$nameofindex	Name of index
	 * @param	int		$mode			0=Use, 1=Force
	 * @return	string					SQL string
	 */
	public function hintindex($nameofindex, $mode = 1)
	{
		return " ".($mode == 1 ? 'FORCE' : 'USE')." INDEX(".preg_replace('/[^a-z0-9_]/', '', $nameofindex).")";
	}


	/**
	 *  Convert a SQL request in Mysql syntax to native syntax
	 *
	 *  @param     string	$line   SQL request line to convert
	 *  @param     string	$type	Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
	 *  @return    string   		SQL request line converted
	 */
	public function convertSQLFromMysql($line, $type = 'ddl')
	{
		return $line;
	}


	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *  Select a database
	 *
	 *  @param	    string	$database	Name of database
	 *  @return	    boolean  		    true if OK, false if KO
	 */
	public function select_db($database)
	{
		// phpcs:enable
		dol_syslog(get_class($this)."::select_db database=".$database, LOG_DEBUG);
		$result = false;
		try {
			$result = $this->db->select_db($database);
		} catch (Exception $e) {
			// Nothing done on error
		}
		return $result;
	}


	/**
	 * Connect to server
	 *
	 * @param   string          $host           Database server host
	 * @param   string          $login          Login
	 * @param   string          $passwd         Password
	 * @param   string          $name           Name of database (not used for mysql, used for pgsql)
	 * @param   integer         $port           Port of database server
	 * @return  mysqli|mysqliDoli|false         Database access object
	 * @see close()
	 */
	public function connect($host, $login, $passwd, $name, $port = 0)
	{
		dol_syslog(get_class($this)."::connect host=$host, port=$port, login=$login, passwd=--hidden--, name=$name", LOG_DEBUG);

		//mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

		$tmp = false;
		try {
			if (!class_exists('mysqli')) {
				dol_print_error(null, 'Driver mysqli for PHP not available');
				return false;
			}
			if (strpos($host, 'ssl://') === 0) {
				$tmp = new mysqliDoli($host, $login, $passwd, $name, $port);
			} else {
				$tmp = new mysqli($host, $login, $passwd, $name, $port);
			}
		} catch (Exception $e) {
			dol_syslog(get_class($this)."::connect failed", LOG_DEBUG);
		}
		return $tmp;
	}

	/**
	 *	Return version of database server
	 *
	 *	@return	        string      Version string
	 */
	public function getVersion()
	{
		return $this->db->server_info;
	}

	/**
	 *	Return version of database client driver
	 *
	 *	@return	        string      Version string
	 */
	public function getDriverInfo()
	{
		return $this->db->client_info;
	}


	/**
	 *  Close database connection
	 *
	 *  @return     bool     True if disconnect successful, false otherwise
	 *  @see        connect()
	 */
	public function close()
	{
		if ($this->db) {
			if ($this->transaction_opened > 0) {
				dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR);
			}
			$this->connected = false;
			return $this->db->close();
		}
		return false;
	}



	/**
	 * 	Execute a SQL request and return the resultset
	 *
	 * 	@param	string	$query			SQL query string
	 * 	@param	int		$usesavepoint	0=Default mode, 1=Run a savepoint before and a rollback to savepoint if error (this allow to have some request with errors inside global transactions).
	 * 									Note that with Mysql, this parameter is not used as Myssql can already commit a transaction even if one request is in error, without using savepoints.
	 *  @param  string	$type           Type of SQL order ('ddl' for insert, update, select, delete or 'dml' for create, alter...)
	 * 	@param	int		$result_mode	Result mode (Using 1=MYSQLI_USE_RESULT instead of 0=MYSQLI_STORE_RESULT will not buffer the result and save memory)
	 *	@return	false|mysqli_result		Resultset of answer
	 */
	public function query($query, $usesavepoint = 0, $type = 'auto', $result_mode = 0)
	{
		global $dolibarr_main_db_readonly;

		$query = trim($query);

		if (!in_array($query, array('BEGIN', 'COMMIT', 'ROLLBACK'))) {
			$SYSLOG_SQL_LIMIT = 10000; // limit log to 10kb per line to limit DOS attacks
			dol_syslog('sql='.substr($query, 0, $SYSLOG_SQL_LIMIT), LOG_DEBUG);
		}
		if (empty($query)) {
			return false; // Return false = error if empty request
		}

		if (!empty($dolibarr_main_db_readonly)) {
			if (preg_match('/^(INSERT|UPDATE|REPLACE|DELETE|CREATE|ALTER|TRUNCATE|DROP)/i', $query)) {
				$this->lasterror = 'Application in read-only mode';
				$this->lasterrno = 'APPREADONLY';
				$this->lastquery = $query;
				return false;
			}
		}

		try {
			$ret = $this->db->query($query, $result_mode);
		} catch (Exception $e) {
			dol_syslog(get_class($this)."::query Exception in query instead of returning an error: ".$e->getMessage(), LOG_ERR);
			$ret = false;
		}

		if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) {
			// Si requete utilisateur, on la sauvegarde ainsi que son resultset
			if (!$ret) {
				$this->lastqueryerror = $query;
				$this->lasterror = $this->error();
				$this->lasterrno = $this->errno();

				if (getDolGlobalInt('SYSLOG_LEVEL') < LOG_DEBUG) {
					dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously
				}
				dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterrno." ".$this->lasterror.self::getCallerInfoString(), LOG_ERR);
				//var_dump(debug_print_backtrace());
			}
			$this->lastquery = $query;
			$this->_results = $ret;
		}

		return $ret;
	}

	/**
	 * Get caller info
	 *
	 * @return string
	 */
	final protected static function getCallerInfoString()
	{
		$backtrace = debug_backtrace();
		$msg = "";
		if (count($backtrace) >= 1) {
			$trace = $backtrace[1];
			if (isset($trace['file'], $trace['line'])) {
				$msg = " From {$trace['file']}:{$trace['line']}.";
			}
		}
		return $msg;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 * 	Returns the current line (as an object) for the resultset cursor
	 *
	 *	@param	mysqli_result	$resultset	Curseur de la requete voulue
	 *	@return	object|null					Object result line or null if KO or end of cursor
	 */
	public function fetch_object($resultset)
	{
		// phpcs:enable
		// If the resultset was not provided, we get the last one for this connection
		if (!is_object($resultset)) {
			$resultset = $this->_results;
		}
		return $resultset->fetch_object();
	}


	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Return data as an array
	 *
	 *	@param	mysqli_result	$resultset	Resultset of request
	 *	@return	array<int|string,mixed>|null|false	Array or null if KO or end of cursor
	 */
	public function fetch_array($resultset)
	{
		// phpcs:enable
		// If resultset not provided, we take the last used by connection
		if (!is_object($resultset)) {
			$resultset = $this->_results;
		}
		return $resultset->fetch_array();
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Return data as an array
	 *
	 *	@param	mysqli_result	$resultset	Resultset of request
	 *	@return	array<int,mixed>|null|int<0,0>	Array or null if KO or end of cursor or 0 if resultset is bool
	 */
	public function fetch_row($resultset)
	{
		// phpcs:enable
		// If resultset not provided, we take the last used by connection
		if (!is_bool($resultset)) {
			if (!is_object($resultset)) {
				$resultset = $this->_results;
			}
			return $resultset->fetch_row();
		} else {
			// si le curseur est un boolean on retourne la valeur 0
			return 0;
		}
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Return number of lines for result of a SELECT
	 *
	 *	@param	mysqli_result	$resultset  Resulset of requests
	 *	@return	int				Nb of lines
	 *	@see    affected_rows()
	 */
	public function num_rows($resultset)
	{
		// phpcs:enable
		// If resultset not provided, we take the last used by connection
		if (!is_object($resultset)) {
			$resultset = $this->_results;
		}
		return isset($resultset->num_rows) ? $resultset->num_rows : 0;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Return the number of lines in the result of a request INSERT, DELETE or UPDATE
	 *
	 *	@param	mysqli_result	$resultset	Curseur de la requete voulue
	 *	@return int							Number of lines
	 *	@see    num_rows()
	 */
	public function affected_rows($resultset)
	{
		// phpcs:enable
		// If resultset not provided, we take the last used by connection
		if (!is_object($resultset)) {
			$resultset = $this->_results;
		}
		// mysql necessite un link de base pour cette fonction contrairement
		// a pqsql qui prend un resultset
		return $this->db->affected_rows;
	}

	/**
	 *	Libere le dernier resultset utilise sur cette connection
	 *
	 *	@param  mysqli_result	$resultset	Curseur de la requete voulue
	 *	@return	void
	 */
	public function free($resultset = null)
	{
		// If resultset not provided, we take the last used by connection
		if (!is_object($resultset)) {
			$resultset = $this->_results;
		}
		// Si resultset en est un, on libere la memoire
		if (is_object($resultset)) {
			$resultset->free_result();
		}
	}

	/**
	 *	Escape a string to insert data
	 *
	 *	@param	string	$stringtoencode		String to escape
	 *	@return	string						String escaped
	 */
	public function escape($stringtoencode)
	{
		return $this->db->real_escape_string((string) $stringtoencode);
	}

	/**
	 *	Escape a string to insert data into a like
	 *
	 *	@param	string	$stringtoencode		String to escape
	 *	@return	string						String escaped
	 */
	public function escapeforlike($stringtoencode)
	{
		// We must first replace the \ char into \\, then we can replace _ and % into \_ and \%
		return str_replace(array('\\', '_', '%'), array('\\\\', '\_', '\%'), (string) $stringtoencode);
	}

	/**
	 *	Return generic error code of last operation.
	 *
	 *	@return	string		Error code (Examples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
	 */
	public function errno()
	{
		if (!$this->connected) {
			// Si il y a eu echec de connection, $this->db n'est pas valide.
			return 'DB_ERROR_FAILED_TO_CONNECT';
		} else {
			// Constants to convert a MySql error code to a generic Dolibarr error code
			$errorcode_map = array(
				1004 => 'DB_ERROR_CANNOT_CREATE',
				1005 => 'DB_ERROR_CANNOT_CREATE',
				1006 => 'DB_ERROR_CANNOT_CREATE',
				1007 => 'DB_ERROR_ALREADY_EXISTS',
				1008 => 'DB_ERROR_CANNOT_DROP',
				1022 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
				1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
				1044 => 'DB_ERROR_ACCESSDENIED',
				1046 => 'DB_ERROR_NODBSELECTED',
				1048 => 'DB_ERROR_CONSTRAINT',
				1050 => 'DB_ERROR_TABLE_ALREADY_EXISTS',
				1051 => 'DB_ERROR_NOSUCHTABLE',
				1054 => 'DB_ERROR_NOSUCHFIELD',
				1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
				1061 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
				1062 => 'DB_ERROR_RECORD_ALREADY_EXISTS',
				1064 => 'DB_ERROR_SYNTAX',
				1068 => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
				1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
				1091 => 'DB_ERROR_NOSUCHFIELD',
				1100 => 'DB_ERROR_NOT_LOCKED',
				1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
				1146 => 'DB_ERROR_NOSUCHTABLE',
				1215 => 'DB_ERROR_CANNOT_ADD_FOREIGN_KEY_CONSTRAINT',
				1216 => 'DB_ERROR_NO_PARENT',
				1217 => 'DB_ERROR_CHILD_EXISTS',
				1396 => 'DB_ERROR_USER_ALREADY_EXISTS', // When creating a user that already existing
				1451 => 'DB_ERROR_CHILD_EXISTS',
				1826 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS'
			);

			if (isset($errorcode_map[$this->db->errno])) {
				return $errorcode_map[$this->db->errno];
			}
			$errno = $this->db->errno;
			return ($errno ? 'DB_ERROR_'.$errno : '0');
		}
	}

	/**
	 *	Return description of last error
	 *
	 *	@return	string		Error text
	 */
	public function error()
	{
		if (!$this->connected) {
			// Si il y a eu echec de connection, $this->db n'est pas valide pour mysqli_error.
			return 'Not connected. Check setup parameters in conf/conf.php file and your mysql client and server versions';
		} else {
			return $this->db->error;
		}
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 * Get last ID after an insert INSERT
	 *
	 * @param   string	$tab    	Table name concerned by insert. Use of this could be avoided with MySql as last inset id is stored into the Mysqli object but we use it for compatibility with Postgresql
	 * @param	string	$fieldid	Field name
	 * @return  int|string			Id of row
	 */
	public function last_insert_id($tab, $fieldid = 'rowid')
	{
		// phpcs:enable
		return $this->db->insert_id;
	}

	/**
	 * Encrypt sensitive data in database
	 * Warning: This function includes the escape and add the SQL simple quotes on strings.
	 *
	 * @param	string	$fieldorvalue	Field name or value to encrypt
	 * @param	int		$withQuotes		Return string including the SQL simple quotes. This param must always be 1 (Value 0 is bugged and deprecated).
	 * @return	string					XXX(field) or XXX('value') or field or 'value'
	 */
	public function encrypt($fieldorvalue, $withQuotes = 1)
	{
		global $conf;

		// Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
		$cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);

		//Encryption key
		$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');

		$escapedstringwithquotes = ($withQuotes ? "'" : "").$this->escape($fieldorvalue).($withQuotes ? "'" : "");

		if ($cryptType && !empty($cryptKey)) {
			if ($cryptType == 2) {
				$escapedstringwithquotes = "AES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
			} elseif ($cryptType == 1) {
				$escapedstringwithquotes = "DES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
			}
		}

		return $escapedstringwithquotes;
	}

	/**
	 *	Decrypt sensitive data in database
	 *
	 *	@param	string	$value			Value to decrypt
	 * 	@return	string					Decrypted value if used
	 */
	public function decrypt($value)
	{
		global $conf;

		// Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
		$cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);

		//Encryption key
		$cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');

		$return = $value;

		if ($cryptType && !empty($cryptKey)) {
			if ($cryptType == 2) {
				$return = 'AES_DECRYPT('.$value.',\''.$cryptKey.'\')';
			} elseif ($cryptType == 1) {
				$return = 'DES_DECRYPT('.$value.',\''.$cryptKey.'\')';
			}
		}

		return $return;
	}


	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 * Return connection ID
	 *
	 * @return	        string      Id connection
	 */
	public function DDLGetConnectId()
	{
		// phpcs:enable
		$resql = $this->query('SELECT CONNECTION_ID()');
		if ($resql) {
			$row = $this->fetch_row($resql);
			return $row[0];
		} else {
			return '?';
		}
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *  Create a new database
	 *	Do not use function xxx_create_db (xxx=mysql, ...) as they are deprecated
	 *	We force to create database with charset this->forcecharset and collate this->forcecollate
	 *
	 *	@param	string	$database		Database name to create
	 * 	@param	string	$charset		Charset used to store data
	 * 	@param	string	$collation		Charset used to sort data
	 * 	@param	string	$owner			Username of database owner
	 * 	@return	null|mysqli_result		Resource defined if OK, null if KO
	 */
	public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
	{
		// phpcs:enable
		if (empty($charset)) {
			$charset = $this->forcecharset;
		}
		if (empty($collation)) {
			$collation = $this->forcecollate;
		}

		// ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci
		$sql = "CREATE DATABASE `".$this->escape($database)."`";
		$sql .= " DEFAULT CHARACTER SET `".$this->escape($charset)."` DEFAULT COLLATE `".$this->escape($collation)."`";

		dol_syslog($sql, LOG_DEBUG);
		$ret = $this->query($sql);
		if (!$ret) {
			// We try again for compatibility with Mysql < 4.1.1
			$sql = "CREATE DATABASE `".$this->escape($database)."`";
			dol_syslog($sql, LOG_DEBUG);
			$ret = $this->query($sql);
		}

		return $ret;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *  List tables into a database
	 *
	 *  @param	string		$database	Name of database
	 *  @param	string		$table		Name of table filter ('xxx%')
	 *  @return	string[]				List of tables in an array
	 */
	public function DDLListTables($database, $table = '')
	{
		// phpcs:enable
		$listtables = array();

		$like = '';
		if ($table) {
			$tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);

			$like = "LIKE '".$this->escape($tmptable)."'";
		}
		$tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);

		$sql = "SHOW TABLES FROM `".$tmpdatabase."` ".$like.";";
		//print $sql;
		$result = $this->query($sql);
		if ($result) {
			while ($row = $this->fetch_row($result)) {
				$listtables[] = $row[0];
			}
		}
		return $listtables;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *  List tables into a database
	 *
	 *  @param	string		$database	Name of database
	 *  @param	string		$table		Name of table filter ('xxx%')
	 *  @return	array<array{0:string,1:string}>		List of tables in an array
	 */
	public function DDLListTablesFull($database, $table = '')
	{
		// phpcs:enable
		$listtables = array();

		$like = '';
		if ($table) {
			$tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);

			$like = "LIKE '".$this->escape($tmptable)."'";
		}
		$tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);

		$sql = "SHOW FULL TABLES FROM `".$tmpdatabase."` ".$like.";";

		$result = $this->query($sql);
		if ($result) {
			while ($row = $this->fetch_row($result)) {
				$listtables[] = $row;
			}
		}
		return $listtables;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	List information of columns in a table.
	 *
	 *	@param	string	$table		Name of table
	 *	@return	array<array<mixed>>		Table with information of columns in the table
	 */
	public function DDLInfoTable($table)
	{
		// phpcs:enable
		$infotables = array();

		$tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);

		$sql = "SHOW FULL COLUMNS FROM ".$tmptable.";";

		dol_syslog($sql, LOG_DEBUG);
		$result = $this->query($sql);
		if ($result) {
			while ($row = $this->fetch_row($result)) {
				$infotables[] = $row;
			}
		}
		return $infotables;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Create a table into database
	 *
	 *	@param	    string	$table 			Name of table
	 *	@param	    array<string,array{type:string,label?:string,enabled?:int<0,2>|string,position?:int,notnull?:int,visible?:int<-2,5>|string,alwayseditable?:int<0,1>,noteditable?:int<0,1>,default?:string,index?:int,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,csslist?:string,help?:string,showoncombobox?:int<0,2>,disabled?:int<0,1>,arrayofkeyval?:array<int,string>,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>}>	$fields 		Tableau associatif [nom champ][tableau des descriptions]
	 *	@param	    string	$primary_key 	Nom du champ qui sera la clef primaire
	 *	@param	    string	$type 			Type de la table
	 *	@param	    ?array<string,mixed>	$unique_keys 	Tableau associatifs Nom de champs qui seront clef unique => valeur
	 *	@param	    string[]	$fulltext_keys	Tableau des Nom de champs qui seront indexes en fulltext
	 *	@param	    array<string,mixed>	$keys 	Tableau des champs cles noms => valeur
	 *	@return	    int						Return integer <0 if KO, >=0 if OK
	 */
	public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
	{
		// phpcs:enable
		// @TODO: $fulltext_keys parameter is unused

		if (empty($type)) {
			$type = 'InnoDB';
		}

		$pk = '';
		$sqlk = array();
		$sqluq = array();

		// Keys found into the array $fields: type,value,attribute,null,default,extra
		// ex. : $fields['rowid'] = array(
		//			'type'=>'int' or 'integer',
		//			'value'=>'11',
		//			'null'=>'not null',
		//			'extra'=> 'auto_increment'
		//		);
		$sql = "CREATE TABLE ".$this->sanitize($table)."(";
		$i = 0;
		$sqlfields = array();
		foreach ($fields as $field_name => $field_desc) {
			$sqlfields[$i] = $this->sanitize($field_name)." ";
			$sqlfields[$i] .= $this->sanitize($field_desc['type']);
			if (isset($field_desc['value']) && $field_desc['value'] !== '') {
				$sqlfields[$i] .= "(".$this->sanitize($field_desc['value']).")";
			}
			if (isset($field_desc['attribute']) && $field_desc['attribute'] !== '') {
				$sqlfields[$i] .= " ".$this->sanitize($field_desc['attribute'], 0, 0, 1);	// Allow space to accept attributes like "ON UPDATE CURRENT_TIMESTAMP"
			}
			if (isset($field_desc['default']) && $field_desc['default'] !== '') {
				if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
					$sqlfields[$i] .= " DEFAULT ".((float) $field_desc['default']);
				} elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
					$sqlfields[$i] .= " DEFAULT ".$this->sanitize($field_desc['default']);
				} else {
					$sqlfields[$i] .= " DEFAULT '".$this->escape($field_desc['default'])."'";
				}
			}
			if (isset($field_desc['null']) && $field_desc['null'] !== '') {
				$sqlfields[$i] .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
			}
			if (isset($field_desc['extra']) && $field_desc['extra'] !== '') {
				$sqlfields[$i] .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
			}
			if (!empty($primary_key) && $primary_key == $field_name) {
				$sqlfields[$i] .= " AUTO_INCREMENT PRIMARY KEY";	// mysql instruction that will be converted by driver late
			}
			$i++;
		}

		if (is_array($unique_keys)) {
			$i = 0;
			foreach ($unique_keys as $key => $value) {
				$sqluq[$i] = "UNIQUE KEY '".$this->sanitize($key)."' ('".$this->escape($value)."')";
				$i++;
			}
		}
		if (is_array($keys)) {
			$i = 0;
			foreach ($keys as $key => $value) {
				$sqlk[$i] = "KEY ".$this->sanitize($key)." (".$value.")";
				$i++;
			}
		}
		$sql .= implode(', ', $sqlfields);
		if (!is_array($unique_keys) && $unique_keys != "") {
			$sql .= ",".implode(',', $sqluq);
		}
		if (is_array($keys)) {
			$sql .= ",".implode(',', $sqlk);
		}
		$sql .= ")";
		$sql .= " engine=".$this->sanitize($type);

		if (!$this->query($sql)) {
			return -1;
		} else {
			return 1;
		}
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *  Drop a table into database
	 *
	 *	@param	    string	$table 			Name of table
	 *	@return	    int						Return integer <0 if KO, >=0 if OK
	 */
	public function DDLDropTable($table)
	{
		// phpcs:enable
		$tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);

		$sql = "DROP TABLE ".$this->sanitize($tmptable);

		if (!$this->query($sql)) {
			return -1;
		} else {
			return 1;
		}
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Return a pointer of line with description of a table or field
	 *
	 *	@param	string		$table	Name of table
	 *	@param	string		$field	Optionnel : Name of field if we want description of field
	 *	@return	bool|mysqli_result	Resultset x (x->Field, x->Type, ...)
	 */
	public function DDLDescTable($table, $field = "")
	{
		// phpcs:enable
		$sql = "DESC ".$this->sanitize($table)." ".$this->sanitize($field);

		dol_syslog(get_class($this)."::DDLDescTable ".$sql, LOG_DEBUG);
		$this->_results = $this->query($sql);
		return $this->_results;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Create a new field into table
	 *
	 *	@param	string	$table 				Name of table
	 *	@param	string	$field_name 		Name of field to add
	 *	@param	array{type:string,label?:string,enabled?:int<0,2>|string,position?:int,notnull?:int,visible?:int,noteditable?:int,default?:string,extra?:string,null?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string} $field_desc 		Associative array of description of the field to insert [parameter name][parameter value]
	 *	@param	string	$field_position 	Optional e.g.: "after some_field"
	 *	@return	int							Return integer <0 if KO, >0 if OK
	 */
	public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
	{
		// phpcs:enable
		// cles recherchees dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra
		// ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
		$sql = "ALTER TABLE ".$this->sanitize($table)." ADD ".$this->sanitize($field_name)." ";

		if ($field_desc['type'] !== 'datetimegmt') {
			$sql .= $this->sanitize($field_desc['type']);
		} else {
			$sql .= 'datetime';
		}

		if (isset($field_desc['value']) && preg_match("/^[^\s]/i", $field_desc['value'])) {
			if (!in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'date', 'datetime', 'datetimegmt')) && $field_desc['value']) {
				$sql .= "(".$this->sanitize($field_desc['value']).")";
			}
		}
		if (isset($field_desc['attribute']) && preg_match("/^[^\s]/i", $field_desc['attribute'])) {
			$sql .= " ".$this->sanitize($field_desc['attribute']);
		}
		if (isset($field_desc['null']) && preg_match("/^[^\s]/i", $field_desc['null'])) {
			if ($field_desc['null'] == 'NOT NULL') {
				$sql .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
			} else {
				$sql .= " ".$this->sanitize($field_desc['null']);
			}
		}
		if (isset($field_desc['default']) && preg_match("/^[^\s]/i", $field_desc['default'])) {
			if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
				$sql .= " DEFAULT ".((float) $field_desc['default']);
			} elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
				$sql .= " DEFAULT ".$this->sanitize($field_desc['default']);
			} else {
				$sql .= " DEFAULT '".$this->escape($field_desc['default'])."'";
			}
		}
		if (isset($field_desc['extra']) && preg_match("/^[^\s]/i", $field_desc['extra'])) {
			$sql .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
		}
		$sql .= " ".$this->sanitize($field_position, 0, 0, 1);

		dol_syslog(get_class($this)."::DDLAddField ".$sql, LOG_DEBUG);
		if ($this->query($sql)) {
			return 1;
		}
		return -1;
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Update format of a field into a table
	 *
	 *	@param	string	$table 				Name of table
	 *	@param	string	$field_name 		Name of field to modify
	 *	@param	array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string,value?:string,null?:string}	$field_desc 		Array with description of field format
	 *	@return	int							Return integer <0 if KO, >0 if OK
	 */
	public function DDLUpdateField($table, $field_name, $field_desc)
	{
		// phpcs:enable
		$sql = "ALTER TABLE ".$this->sanitize($table);
		$sql .= " MODIFY COLUMN ".$this->sanitize($field_name)." ";

		if ($field_desc['type'] !== 'datetimegmt') {
			$sql .= $this->sanitize($field_desc['type']);
		} else {
			$sql .= 'datetime';
		}

		if (in_array($field_desc['type'], array('double', 'tinyint', 'int', 'varchar')) && array_key_exists('value', $field_desc) && $field_desc['value']) {
			$sql .= "(".$this->sanitize($field_desc['value']).")";
		}
		if (isset($field_desc['null']) && ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')) {
			// We will try to change format of column to NOT NULL. To be sure the ALTER works, we try to update fields that are NULL
			if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text') {
				$sqlbis = "UPDATE ".$this->sanitize($table)." SET ".$this->sanitize($field_name)." = '".$this->escape(isset($field_desc['default']) ? $field_desc['default'] : '')."' WHERE ".$this->sanitize($field_name)." IS NULL";
				$this->query($sqlbis);
			} elseif (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
				$sqlbis = "UPDATE ".$this->sanitize($table)." SET ".$this->sanitize($field_name)." = ".((float) $this->escape(isset($field_desc['default']) ? $field_desc['default'] : 0))." WHERE ".$this->sanitize($field_name)." IS NULL";
				$this->query($sqlbis);
			}

			$sql .= " NOT NULL";
		}

		if (isset($field_desc['default']) && $field_desc['default'] != '') {
			if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
				$sql .= " DEFAULT ".((float) $field_desc['default']);
			} elseif ($field_desc['type'] != 'text') {
				$sql .= " DEFAULT '".$this->escape($field_desc['default'])."'"; // Default not supported on text fields
			}
		}

		dol_syslog(get_class($this)."::DDLUpdateField ".$sql, LOG_DEBUG);
		if (!$this->query($sql)) {
			return -1;
		} else {
			return 1;
		}
	}

	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 *	Drop a field from table
	 *
	 *	@param	string	$table 			Name of table
	 *	@param	string	$field_name 	Name of field to drop
	 *	@return	int						Return integer <0 if KO, >0 if OK
	 */
	public function DDLDropField($table, $field_name)
	{
		// phpcs:enable
		$tmp_field_name = preg_replace('/[^a-z0-9\.\-\_]/i', '', $field_name);

		$sql = "ALTER TABLE ".$this->sanitize($table)." DROP COLUMN `".$this->sanitize($tmp_field_name)."`";
		if ($this->query($sql)) {
			return 1;
		}
		$this->error = $this->lasterror();
		return -1;
	}


	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
	/**
	 * 	Create a user and privileges to connect to database (even if database does not exists yet)
	 *
	 *	@param	string	$dolibarr_main_db_host 		Ip server or '%'
	 *	@param	string	$dolibarr_main_db_user 		Nom new user
	 *	@param	string	$dolibarr_main_db_pass 		Password for the new user
	 *	@param	string	$dolibarr_main_db_name		Database name where user must be granted
	 *	@return	int									Return integer <0 if KO, >=0 if OK
	 */
	public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
	{
		// phpcs:enable
		$sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
		dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
		$resql = $this->query($sql);
		if (!$resql) {
			if ($this->lasterrno != 'DB_ERROR_USER_ALREADY_EXISTS') {
				return -1;
			} else {
				// If user already exists, we continue to set permissions
				dol_syslog(get_class($this)."::DDLCreateUser sql=".$sql, LOG_WARNING);
			}
		}

		// Redo with localhost forced (sometimes user is created on %)
		$sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."'@'localhost' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
		$resql = $this->query($sql);

		$sql = "GRANT ALL PRIVILEGES ON ".$this->escape($dolibarr_main_db_name).".* TO '".$this->escape($dolibarr_main_db_user)."'@'".$this->escape($dolibarr_main_db_host)."'";
		dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
		$resql = $this->query($sql);
		if (!$resql) {
			$this->error = "Connected user not allowed to GRANT ALL PRIVILEGES ON ".$this->escape($dolibarr_main_db_name).".* TO '".$this->escape($dolibarr_main_db_user)."'@'".$this->escape($dolibarr_main_db_host)."'";
			return -1;
		}

		$sql = "FLUSH Privileges";

		dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG);
		$resql = $this->query($sql);
		if (!$resql) {
			return -1;
		}

		return 1;
	}

	/**
	 *	Return charset used to store data in current database
	 *  Note: if we are connected to databasename, it is same result than using SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = "databasename";)
	 *
	 *	@return		string		Charset
	 *  @see getDefaultCollationDatabase()
	 */
	public function getDefaultCharacterSetDatabase()
	{
		$resql = $this->query("SHOW VARIABLES LIKE 'character_set_database'");
		if (!$resql) {
			// version Mysql < 4.1.1
			return $this->forcecharset;
		}
		$liste = $this->fetch_array($resql);
		$tmpval = $liste['Value'];

		return $tmpval;
	}

	/**
	 *	Return list of available charset that can be used to store data in database
	 *
	 *	@return	?array<int,array{charset:string,description:string}>		List of Charset
	 */
	public function getListOfCharacterSet()
	{
		$resql = $this->query('SHOW CHARSET');
		$liste = array();
		if ($resql) {
			$i = 0;
			while ($obj = $this->fetch_object($resql)) {
				$liste[$i]['charset'] = $obj->Charset;
				$liste[$i]['description'] = $obj->Description;
				$i++;
			}
			$this->free($resql);
		} else {
			// version Mysql < 4.1.1
			return null;
		}
		return $liste;
	}

	/**
	 *	Return collation used in current database
	 *
	 *	@return		string		Collation value
	 *  @see getDefaultCharacterSetDatabase()
	 */
	public function getDefaultCollationDatabase()
	{
		$resql = $this->query("SHOW VARIABLES LIKE 'collation_database'");
		if (!$resql) {
			// version Mysql < 4.1.1
			return $this->forcecollate;
		}
		$liste = $this->fetch_array($resql);
		$tmpval = $liste['Value'];

		return $tmpval;
	}

	/**
	 *	Return list of available collation that can be used for database
	 *
	 *	@return		?array<int,array{collation:string}>		List of Collations
	 */
	public function getListOfCollation()
	{
		$resql = $this->query('SHOW COLLATION');
		$liste = array();
		if ($resql) {
			$i = 0;
			while ($obj = $this->fetch_object($resql)) {
				$liste[$i]['collation'] = $obj->Collation;
				$i++;
			}
			$this->free($resql);
		} else {
			// version Mysql < 4.1.1
			return null;
		}
		return $liste;
	}

	/**
	 *	Return full path of dump program
	 *
	 *	@return		string		Full path of dump program
	 */
	public function getPathOfDump()
	{
		$fullpathofdump = '/pathtomysqldump/mysqldump';

		$resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
		if ($resql) {
			$liste = $this->fetch_array($resql);
			$basedir = $liste['Value'];
			$fullpathofdump = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysqldump';
		}
		return $fullpathofdump;
	}

	/**
	 *	Return full path of restore program
	 *
	 *	@return		string		Full path of restore program
	 */
	public function getPathOfRestore()
	{
		$fullpathofimport = '/pathtomysql/mysql';

		$resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
		if ($resql) {
			$liste = $this->fetch_array($resql);
			$basedir = $liste['Value'];
			$fullpathofimport = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysql';
		}
		return $fullpathofimport;
	}

	/**
	 * Return value of server parameters
	 *
	 * @param	string	$filter			Filter list on a particular value
	 * @return	array<string,string>	Array of key-values (key=>value)
	 */
	public function getServerParametersValues($filter = '')
	{
		$result = array();

		$sql = 'SHOW VARIABLES';
		if ($filter) {
			$sql .= " LIKE '".$this->escape($filter)."'";
		}
		$resql = $this->query($sql);
		if ($resql) {
			while ($obj = $this->fetch_object($resql)) {
				$result[$obj->Variable_name] = $obj->Value;
			}
		}

		return $result;
	}

	/**
	 * Return value of server status (current indicators on memory, cache...)
	 *
	 * @param	string	$filter			Filter list on a particular value
	 * @return  array<string,string>	Array of key-values (key=>value)
	 */
	public function getServerStatusValues($filter = '')
	{
		$result = array();

		$sql = 'SHOW STATUS';
		if ($filter) {
			$sql .= " LIKE '".$this->escape($filter)."'";
		}
		$resql = $this->query($sql);
		if ($resql) {
			while ($obj = $this->fetch_object($resql)) {
				$result[$obj->Variable_name] = $obj->Value;
			}
		}

		return $result;
	}

	/**
	 * Get the last ID of an auto-increment field of a table
	 *
	 * @param 	string 		$table 	Name of table
	 * @return 	int		 			Next ID or < 0 if error
	 */
	public function getNextAutoIncrementId($table)
	{
		// Request to get last status of table
		$sql = "SHOW TABLE STATUS LIKE '".$this->escape($table)."'";
		$result = $this->query($sql);

		if ($result) {
			$obj = $this->fetch_object($result);
			if ($obj && isset($obj->Auto_increment)) {
				return (int) $obj->Auto_increment;
			}
		}

		return -1;
	}

	/**
	 * Prepare a SQL statement for execution
	 *
	 * @param string $sql SQL query to prepare
	 * @return false|mysqli_stmt
	 */
	public function prepare($sql)
	{
		if (!$this->connected) {
			$this->lasterror = 'Not connected to database';
			return false;
		}
		$stmt = $this->db->prepare($sql);
		if ($stmt === false) {
			$this->lasterror = $this->db->error;
			$this->lastqueryerror = $sql;
			return false;
		}

		return $stmt;
	}
}

if (class_exists('mysqli')) {
	/**
	 * Class to make SSL connection
	 */
	class mysqliDoli extends mysqli
	{
		/**
		 *	Constructor.
		 *	This create an opened connection to a database server and eventually to a database
		 *
		 *	@param	    string	$host		Address of database server
		 *	@param	    string	$user		Name of database user
		 *	@param	    string	$pass		Password of database user
		 *	@param	    string	$name		Name of database
		 *	@param	    int		$port		Port of database server
		 *	@param	    string	$socket		Socket
		 */
		public function __construct($host, $user, $pass, $name, $port = 0, $socket = "")
		{
			$flags = 0;
			if (PHP_VERSION_ID >= 80100) {
				parent::__construct();
			} else {
				// @phan-suppress-next-line PhanDeprecatedFunctionInternal
				parent::init();
			}
			if (strpos($host, 'ssl://') === 0) {
				$host = substr($host, 6);
				parent::options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, false);
				// Suppress false positive @phan-suppress-next-line PhanTypeMismatchArgumentInternalProbablyReal
				parent::ssl_set(null, null, "", null, null);
				$flags = MYSQLI_CLIENT_SSL;
			}
			parent::real_connect($host, $user, $pass, $name, $port, $socket, $flags);
		}
	}
}