dolibarr 21.0.0-alpha
mysqli.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001 Fabien Seisen <seisen@linuxfr.org>
3 * Copyright (C) 2002-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
4 * Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2015 Raphaƫl Doursenaud <rdoursenaud@gpcsolutions.fr>
8 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2024 Charlene Benke <charlene@patas-monkey.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
30require_once DOL_DOCUMENT_ROOT.'/core/db/DoliDB.class.php';
31
35class DoliDBMysqli extends DoliDB
36{
38 public $db;
40 public $type = 'mysqli';
41
43 const LABEL = 'MySQL or MariaDB';
45 const VERSIONMIN = '5.0.3';
46
48 private $_results;
49
61 public function __construct($type, $host, $user, $pass, $name = '', $port = 0)
62 {
63 global $conf, $langs;
64
65 // Note that having "static" property for "$forcecharset" and "$forcecollate" will make error here in strict mode, so they are not static
66 if (!empty($conf->db->character_set)) {
67 $this->forcecharset = $conf->db->character_set;
68 }
69 if (!empty($conf->db->dolibarr_main_db_collation)) {
70 $this->forcecollate = $conf->db->dolibarr_main_db_collation;
71 }
72
73 $this->database_user = $user;
74 $this->database_host = $host;
75 $this->database_port = $port;
76
77 $this->transaction_opened = 0;
78
79 //print "Name DB: $host,$user,$pass,$name<br>";
80
81 if (!class_exists('mysqli')) {
82 $this->connected = false;
83 $this->ok = false;
84 $this->error = "Mysqli PHP functions for using Mysqli driver are not available in this version of PHP. Try to use another driver.";
85 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);
86 }
87
88 if (!$host) {
89 $this->connected = false;
90 $this->ok = false;
91 $this->error = $langs->trans("ErrorWrongHostParameter");
92 dol_syslog(get_class($this)."::DoliDBMysqli : Connect error, wrong host parameters", LOG_ERR);
93 }
94
95 // Try server connection
96 // We do not try to connect to database, only to server. Connect to database is done later in constructor
97 $this->db = $this->connect($host, $user, $pass, '', $port);
98
99 if ($this->db && empty($this->db->connect_errno)) {
100 $this->connected = true;
101 $this->ok = true;
102 } else {
103 $this->connected = false;
104 $this->ok = false;
105 $this->error = empty($this->db) ? 'Failed to connect' : $this->db->connect_error;
106 dol_syslog(get_class($this)."::DoliDBMysqli Connect error: ".$this->error, LOG_ERR);
107 }
108
109 $disableforcecharset = 0; // Set to 1 to test without charset forcing
110
111 // If server connection is ok, we try to connect to the database
112 if ($this->connected && $name) {
113 if ($this->select_db($name)) {
114 $this->database_selected = true;
115 $this->database_name = $name;
116 $this->ok = true;
117
118 // If client is old latin, we force utf8
119 $clientmustbe = empty($conf->db->character_set) ? 'utf8' : (string) $conf->db->character_set;
120 if (preg_match('/latin1/', $clientmustbe)) {
121 $clientmustbe = 'utf8';
122 }
123
124 if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
125 try {
126 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);
127 // To get current charset: USE databasename; SHOW VARIABLES LIKE 'character_set_database'
128 // or: USE databasename; SELECT schema_name, default_character_set_name FROM information_schema.SCHEMATA;
129 // To get current collation: USE databasename; SHOW VARIABLES LIKE 'collation_database'
130 // or: USE databasename; SELECT schema_name, default_character_set_name FROM information_schema.SCHEMATA;
131 // To upgrade database default, you can do: ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
132
133 $this->db->set_charset($clientmustbe); // This set charset, but with a bad collation (colllation is forced later)
134 } catch (Exception $e) {
135 print 'Failed to force character_set_client to '.$clientmustbe." (according to setup) to match the one of the server database.<br>\n";
136 print $e->getMessage();
137 print "<br>\n";
138 if ($clientmustbe != 'utf8') {
139 print 'Edit conf/conf.php file to set a charset "utf8"';
140 if ($clientmustbe != 'utf8mb4') {
141 print ' or "utf8mb4"';
142 }
143 print ' instead of "'.$clientmustbe.'".'."\n";
144 }
145 exit;
146 }
147
148 $collation = (empty($conf) ? 'utf8_unicode_ci' : $conf->db->dolibarr_main_db_collation);
149 if (preg_match('/latin1/', $collation)) {
150 $collation = 'utf8_unicode_ci';
151 }
152
153 if (!preg_match('/general/', $collation)) {
154 $this->db->query("SET collation_connection = ".$collation);
155 }
156 }
157 } else {
158 $this->database_selected = false;
159 $this->database_name = '';
160 $this->ok = false;
161 $this->error = $this->error();
162 dol_syslog(get_class($this)."::DoliDBMysqli : Select_db error ".$this->error, LOG_ERR);
163 }
164 } else {
165 // No selection of database done. We may only be connected or not (ok or ko) to the server.
166 $this->database_selected = false;
167
168 if ($this->connected) {
169 // If client is old latin, we force utf8
170 $clientmustbe = empty($conf->db->character_set) ? 'utf8' : (string) $conf->db->character_set;
171 if (preg_match('/latin1/', $clientmustbe)) {
172 $clientmustbe = 'utf8';
173 }
174 if (preg_match('/utf8mb4/', $clientmustbe)) {
175 $clientmustbe = 'utf8';
176 }
177
178 if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
179 $this->db->set_charset($clientmustbe); // This set utf8_unicode_ci
180
181 $collation = $conf->db->dolibarr_main_db_collation;
182 if (preg_match('/latin1/', $collation)) {
183 $collation = 'utf8_unicode_ci';
184 }
185 if (preg_match('/utf8mb4/', $collation)) {
186 $collation = 'utf8_unicode_ci';
187 }
188
189 if (!preg_match('/general/', $collation)) {
190 $this->db->query("SET collation_connection = ".$collation);
191 }
192 }
193 }
194 }
195 }
196
197
204 public function hintindex($nameofindex)
205 {
206 return " FORCE INDEX(".preg_replace('/[^a-z0-9_]/', '', $nameofindex).")";
207 }
208
209
217 public function convertSQLFromMysql($line, $type = 'ddl')
218 {
219 return $line;
220 }
221
222
223 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
230 public function select_db($database)
231 {
232 // phpcs:enable
233 dol_syslog(get_class($this)."::select_db database=".$database, LOG_DEBUG);
234 $result = false;
235 try {
236 $result = $this->db->select_db($database);
237 } catch (Exception $e) {
238 // Nothing done on error
239 }
240 return $result;
241 }
242
243
255 public function connect($host, $login, $passwd, $name, $port = 0)
256 {
257 dol_syslog(get_class($this)."::connect host=$host, port=$port, login=$login, passwd=--hidden--, name=$name", LOG_DEBUG);
258
259 //mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
260
261 $tmp = false;
262 try {
263 if (!class_exists('mysqli')) {
264 dol_print_error(null, 'Driver mysqli for PHP not available');
265 return false;
266 }
267 if (strpos($host, 'ssl://') === 0) {
268 $tmp = new mysqliDoli($host, $login, $passwd, $name, $port);
269 } else {
270 $tmp = new mysqli($host, $login, $passwd, $name, $port);
271 }
272 } catch (Exception $e) {
273 dol_syslog(get_class($this)."::connect failed", LOG_DEBUG);
274 }
275 return $tmp;
276 }
277
283 public function getVersion()
284 {
285 return $this->db->server_info;
286 }
287
293 public function getDriverInfo()
294 {
295 return $this->db->client_info;
296 }
297
298
305 public function close()
306 {
307 if ($this->db) {
308 if ($this->transaction_opened > 0) {
309 dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR);
310 }
311 $this->connected = false;
312 return $this->db->close();
313 }
314 return false;
315 }
316
317
318
329 public function query($query, $usesavepoint = 0, $type = 'auto', $result_mode = 0)
330 {
331 global $dolibarr_main_db_readonly;
332
333 $query = trim($query);
334
335 if (!in_array($query, array('BEGIN', 'COMMIT', 'ROLLBACK'))) {
336 $SYSLOG_SQL_LIMIT = 10000; // limit log to 10kb per line to limit DOS attacks
337 dol_syslog('sql='.substr($query, 0, $SYSLOG_SQL_LIMIT), LOG_DEBUG);
338 }
339 if (empty($query)) {
340 return false; // Return false = error if empty request
341 }
342
343 if (!empty($dolibarr_main_db_readonly)) {
344 if (preg_match('/^(INSERT|UPDATE|REPLACE|DELETE|CREATE|ALTER|TRUNCATE|DROP)/i', $query)) {
345 $this->lasterror = 'Application in read-only mode';
346 $this->lasterrno = 'APPREADONLY';
347 $this->lastquery = $query;
348 return false;
349 }
350 }
351
352 try {
353 $ret = $this->db->query($query, $result_mode);
354 } catch (Exception $e) {
355 dol_syslog(get_class($this)."::query Exception in query instead of returning an error: ".$e->getMessage(), LOG_ERR);
356 $ret = false;
357 }
358
359 if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) {
360 // Si requete utilisateur, on la sauvegarde ainsi que son resultset
361 if (!$ret) {
362 $this->lastqueryerror = $query;
363 $this->lasterror = $this->error();
364 $this->lasterrno = $this->errno();
365
366 if (getDolGlobalInt('SYSLOG_LEVEL') < LOG_DEBUG) {
367 dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously
368 }
369 dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterrno." ".$this->lasterror.self::getCallerInfoString(), LOG_ERR);
370 //var_dump(debug_print_backtrace());
371 }
372 $this->lastquery = $query;
373 $this->_results = $ret;
374 }
375
376 return $ret;
377 }
378
384 final protected static function getCallerInfoString()
385 {
386 $backtrace = debug_backtrace();
387 $msg = "";
388 if (count($backtrace) >= 1) {
389 $trace = $backtrace[1];
390 if (isset($trace['file'], $trace['line'])) {
391 $msg = " From {$trace['file']}:{$trace['line']}.";
392 }
393 }
394 return $msg;
395 }
396
397 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
404 public function fetch_object($resultset)
405 {
406 // phpcs:enable
407 // If the resultset was not provided, we get the last one for this connection
408 if (!is_object($resultset)) {
409 $resultset = $this->_results;
410 }
411 return $resultset->fetch_object();
412 }
413
414
415 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
422 public function fetch_array($resultset)
423 {
424 // phpcs:enable
425 // If resultset not provided, we take the last used by connection
426 if (!is_object($resultset)) {
427 $resultset = $this->_results;
428 }
429 return $resultset->fetch_array();
430 }
431
432 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
439 public function fetch_row($resultset)
440 {
441 // phpcs:enable
442 // If resultset not provided, we take the last used by connection
443 if (!is_bool($resultset)) {
444 if (!is_object($resultset)) {
445 $resultset = $this->_results;
446 }
447 return $resultset->fetch_row();
448 } else {
449 // si le curseur est un boolean on retourne la valeur 0
450 return 0;
451 }
452 }
453
454 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
462 public function num_rows($resultset)
463 {
464 // phpcs:enable
465 // If resultset not provided, we take the last used by connection
466 if (!is_object($resultset)) {
467 $resultset = $this->_results;
468 }
469 return isset($resultset->num_rows) ? $resultset->num_rows : 0;
470 }
471
472 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
480 public function affected_rows($resultset)
481 {
482 // phpcs:enable
483 // If resultset not provided, we take the last used by connection
484 if (!is_object($resultset)) {
485 $resultset = $this->_results;
486 }
487 // mysql necessite un link de base pour cette fonction contrairement
488 // a pqsql qui prend un resultset
489 return $this->db->affected_rows;
490 }
491
498 public function free($resultset = null)
499 {
500 // If resultset not provided, we take the last used by connection
501 if (!is_object($resultset)) {
502 $resultset = $this->_results;
503 }
504 // Si resultset en est un, on libere la memoire
505 if (is_object($resultset)) {
506 $resultset->free_result();
507 }
508 }
509
516 public function escape($stringtoencode)
517 {
518 return $this->db->real_escape_string((string) $stringtoencode);
519 }
520
527 public function escapeforlike($stringtoencode)
528 {
529 // We must first replace the \ char into \\, then we can replace _ and % into \_ and \%
530 return str_replace(array('\\', '_', '%'), array('\\\\', '\_', '\%'), (string) $stringtoencode);
531 }
532
538 public function errno()
539 {
540 if (!$this->connected) {
541 // Si il y a eu echec de connection, $this->db n'est pas valide.
542 return 'DB_ERROR_FAILED_TO_CONNECT';
543 } else {
544 // Constants to convert a MySql error code to a generic Dolibarr error code
545 $errorcode_map = array(
546 1004 => 'DB_ERROR_CANNOT_CREATE',
547 1005 => 'DB_ERROR_CANNOT_CREATE',
548 1006 => 'DB_ERROR_CANNOT_CREATE',
549 1007 => 'DB_ERROR_ALREADY_EXISTS',
550 1008 => 'DB_ERROR_CANNOT_DROP',
551 1022 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
552 1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
553 1044 => 'DB_ERROR_ACCESSDENIED',
554 1046 => 'DB_ERROR_NODBSELECTED',
555 1048 => 'DB_ERROR_CONSTRAINT',
556 1050 => 'DB_ERROR_TABLE_ALREADY_EXISTS',
557 1051 => 'DB_ERROR_NOSUCHTABLE',
558 1054 => 'DB_ERROR_NOSUCHFIELD',
559 1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
560 1061 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
561 1062 => 'DB_ERROR_RECORD_ALREADY_EXISTS',
562 1064 => 'DB_ERROR_SYNTAX',
563 1068 => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
564 1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
565 1091 => 'DB_ERROR_NOSUCHFIELD',
566 1100 => 'DB_ERROR_NOT_LOCKED',
567 1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
568 1146 => 'DB_ERROR_NOSUCHTABLE',
569 1215 => 'DB_ERROR_CANNOT_ADD_FOREIGN_KEY_CONSTRAINT',
570 1216 => 'DB_ERROR_NO_PARENT',
571 1217 => 'DB_ERROR_CHILD_EXISTS',
572 1396 => 'DB_ERROR_USER_ALREADY_EXISTS', // When creating a user that already existing
573 1451 => 'DB_ERROR_CHILD_EXISTS',
574 1826 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS'
575 );
576
577 if (isset($errorcode_map[$this->db->errno])) {
578 return $errorcode_map[$this->db->errno];
579 }
580 $errno = $this->db->errno;
581 return ($errno ? 'DB_ERROR_'.$errno : '0');
582 }
583 }
584
590 public function error()
591 {
592 if (!$this->connected) {
593 // Si il y a eu echec de connection, $this->db n'est pas valide pour mysqli_error.
594 return 'Not connected. Check setup parameters in conf/conf.php file and your mysql client and server versions';
595 } else {
596 return $this->db->error;
597 }
598 }
599
600 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
608 public function last_insert_id($tab, $fieldid = 'rowid')
609 {
610 // phpcs:enable
611 return $this->db->insert_id;
612 }
613
622 public function encrypt($fieldorvalue, $withQuotes = 1)
623 {
624 global $conf;
625
626 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
627 $cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);
628
629 //Encryption key
630 $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
631
632 $escapedstringwithquotes = ($withQuotes ? "'" : "").$this->escape($fieldorvalue).($withQuotes ? "'" : "");
633
634 if ($cryptType && !empty($cryptKey)) {
635 if ($cryptType == 2) {
636 $escapedstringwithquotes = "AES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
637 } elseif ($cryptType == 1) {
638 $escapedstringwithquotes = "DES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
639 }
640 }
641
642 return $escapedstringwithquotes;
643 }
644
651 public function decrypt($value)
652 {
653 global $conf;
654
655 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
656 $cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);
657
658 //Encryption key
659 $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
660
661 $return = $value;
662
663 if ($cryptType && !empty($cryptKey)) {
664 if ($cryptType == 2) {
665 $return = 'AES_DECRYPT('.$value.',\''.$cryptKey.'\')';
666 } elseif ($cryptType == 1) {
667 $return = 'DES_DECRYPT('.$value.',\''.$cryptKey.'\')';
668 }
669 }
670
671 return $return;
672 }
673
674
675 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
681 public function DDLGetConnectId()
682 {
683 // phpcs:enable
684 $resql = $this->query('SELECT CONNECTION_ID()');
685 if ($resql) {
686 $row = $this->fetch_row($resql);
687 return $row[0];
688 } else {
689 return '?';
690 }
691 }
692
693 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
705 public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
706 {
707 // phpcs:enable
708 if (empty($charset)) {
709 $charset = $this->forcecharset;
710 }
711 if (empty($collation)) {
712 $collation = $this->forcecollate;
713 }
714
715 // ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci
716 $sql = "CREATE DATABASE `".$this->escape($database)."`";
717 $sql .= " DEFAULT CHARACTER SET `".$this->escape($charset)."` DEFAULT COLLATE `".$this->escape($collation)."`";
718
719 dol_syslog($sql, LOG_DEBUG);
720 $ret = $this->query($sql);
721 if (!$ret) {
722 // We try again for compatibility with Mysql < 4.1.1
723 $sql = "CREATE DATABASE `".$this->escape($database)."`";
724 dol_syslog($sql, LOG_DEBUG);
725 $ret = $this->query($sql);
726 }
727
728 return $ret;
729 }
730
731 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
739 public function DDLListTables($database, $table = '')
740 {
741 // phpcs:enable
742 $listtables = array();
743
744 $like = '';
745 if ($table) {
746 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
747
748 $like = "LIKE '".$this->escape($tmptable)."'";
749 }
750 $tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);
751
752 $sql = "SHOW TABLES FROM `".$tmpdatabase."` ".$like.";";
753 //print $sql;
754 $result = $this->query($sql);
755 if ($result) {
756 while ($row = $this->fetch_row($result)) {
757 $listtables[] = $row[0];
758 }
759 }
760 return $listtables;
761 }
762
763 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
771 public function DDLListTablesFull($database, $table = '')
772 {
773 // phpcs:enable
774 $listtables = array();
775
776 $like = '';
777 if ($table) {
778 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
779
780 $like = "LIKE '".$this->escape($tmptable)."'";
781 }
782 $tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);
783
784 $sql = "SHOW FULL TABLES FROM `".$tmpdatabase."` ".$like.";";
785
786 $result = $this->query($sql);
787 if ($result) {
788 while ($row = $this->fetch_row($result)) {
789 $listtables[] = $row;
790 }
791 }
792 return $listtables;
793 }
794
795 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
802 public function DDLInfoTable($table)
803 {
804 // phpcs:enable
805 $infotables = array();
806
807 $tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
808
809 $sql = "SHOW FULL COLUMNS FROM ".$tmptable.";";
810
811 dol_syslog($sql, LOG_DEBUG);
812 $result = $this->query($sql);
813 if ($result) {
814 while ($row = $this->fetch_row($result)) {
815 $infotables[] = $row;
816 }
817 }
818 return $infotables;
819 }
820
821 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
834 public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
835 {
836 // phpcs:enable
837 // @TODO: $fulltext_keys parameter is unused
838
839 if (empty($type)) {
840 $type = 'InnoDB';
841 }
842
843 $pk = '';
844 $sqlk = array();
845 $sqluq = array();
846
847 // Keys found into the array $fields: type,value,attribute,null,default,extra
848 // ex. : $fields['rowid'] = array(
849 // 'type'=>'int' or 'integer',
850 // 'value'=>'11',
851 // 'null'=>'not null',
852 // 'extra'=> 'auto_increment'
853 // );
854 $sql = "CREATE TABLE ".$this->sanitize($table)."(";
855 $i = 0;
856 $sqlfields = array();
857 foreach ($fields as $field_name => $field_desc) {
858 $sqlfields[$i] = $this->sanitize($field_name)." ";
859 $sqlfields[$i] .= $this->sanitize($field_desc['type']);
860 if (isset($field_desc['value']) && $field_desc['value'] !== '') {
861 $sqlfields[$i] .= "(".$this->sanitize($field_desc['value']).")";
862 }
863 if (isset($field_desc['attribute']) && $field_desc['attribute'] !== '') {
864 $sqlfields[$i] .= " ".$this->sanitize($field_desc['attribute'], 0, 0, 1); // Allow space to accept attributes like "ON UPDATE CURRENT_TIMESTAMP"
865 }
866 if (isset($field_desc['default']) && $field_desc['default'] !== '') {
867 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
868 $sqlfields[$i] .= " DEFAULT ".((float) $field_desc['default']);
869 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
870 $sqlfields[$i] .= " DEFAULT ".$this->sanitize($field_desc['default']);
871 } else {
872 $sqlfields[$i] .= " DEFAULT '".$this->escape($field_desc['default'])."'";
873 }
874 }
875 if (isset($field_desc['null']) && $field_desc['null'] !== '') {
876 $sqlfields[$i] .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
877 }
878 if (isset($field_desc['extra']) && $field_desc['extra'] !== '') {
879 $sqlfields[$i] .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
880 }
881 if (!empty($primary_key) && $primary_key == $field_name) {
882 $sqlfields[$i] .= " AUTO_INCREMENT PRIMARY KEY"; // mysql instruction that will be converted by driver late
883 }
884 $i++;
885 }
886
887 if (is_array($unique_keys)) {
888 $i = 0;
889 foreach ($unique_keys as $key => $value) {
890 $sqluq[$i] = "UNIQUE KEY '".$this->sanitize($key)."' ('".$this->escape($value)."')";
891 $i++;
892 }
893 }
894 if (is_array($keys)) {
895 $i = 0;
896 foreach ($keys as $key => $value) {
897 $sqlk[$i] = "KEY ".$this->sanitize($key)." (".$value.")";
898 $i++;
899 }
900 }
901 $sql .= implode(', ', $sqlfields);
902 if (!is_array($unique_keys) && $unique_keys != "") {
903 $sql .= ",".implode(',', $sqluq);
904 }
905 if (is_array($keys)) {
906 $sql .= ",".implode(',', $sqlk);
907 }
908 $sql .= ")";
909 $sql .= " engine=".$this->sanitize($type);
910
911 if (!$this->query($sql)) {
912 return -1;
913 } else {
914 return 1;
915 }
916 }
917
918 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
925 public function DDLDropTable($table)
926 {
927 // phpcs:enable
928 $tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
929
930 $sql = "DROP TABLE ".$this->sanitize($tmptable);
931
932 if (!$this->query($sql)) {
933 return -1;
934 } else {
935 return 1;
936 }
937 }
938
939 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
947 public function DDLDescTable($table, $field = "")
948 {
949 // phpcs:enable
950 $sql = "DESC ".$this->sanitize($table)." ".$this->sanitize($field);
951
952 dol_syslog(get_class($this)."::DDLDescTable ".$sql, LOG_DEBUG);
953 $this->_results = $this->query($sql);
954 return $this->_results;
955 }
956
957 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
967 public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
968 {
969 // phpcs:enable
970 // cles recherchees dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra
971 // ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
972 $sql = "ALTER TABLE ".$this->sanitize($table)." ADD ".$this->sanitize($field_name)." ";
973 $sql .= $this->sanitize($field_desc['type']);
974 if (isset($field_desc['value']) && preg_match("/^[^\s]/i", $field_desc['value'])) {
975 if (!in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'date', 'datetime')) && $field_desc['value']) {
976 $sql .= "(".$this->sanitize($field_desc['value']).")";
977 }
978 }
979 if (isset($field_desc['attribute']) && preg_match("/^[^\s]/i", $field_desc['attribute'])) {
980 $sql .= " ".$this->sanitize($field_desc['attribute']);
981 }
982 if (isset($field_desc['null']) && preg_match("/^[^\s]/i", $field_desc['null'])) {
983 $sql .= " ".$this->sanitize($field_desc['null']);
984 }
985 if (isset($field_desc['default']) && preg_match("/^[^\s]/i", $field_desc['default'])) {
986 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
987 $sql .= " DEFAULT ".((float) $field_desc['default']);
988 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
989 $sql .= " DEFAULT ".$this->sanitize($field_desc['default']);
990 } else {
991 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'";
992 }
993 }
994 if (isset($field_desc['extra']) && preg_match("/^[^\s]/i", $field_desc['extra'])) {
995 $sql .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
996 }
997 $sql .= " ".$this->sanitize($field_position, 0, 0, 1);
998
999 dol_syslog(get_class($this)."::DDLAddField ".$sql, LOG_DEBUG);
1000 if ($this->query($sql)) {
1001 return 1;
1002 }
1003 return -1;
1004 }
1005
1006 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1015 public function DDLUpdateField($table, $field_name, $field_desc)
1016 {
1017 // phpcs:enable
1018 $sql = "ALTER TABLE ".$this->sanitize($table);
1019 $sql .= " MODIFY COLUMN ".$this->sanitize($field_name)." ".$this->sanitize($field_desc['type']);
1020 if (in_array($field_desc['type'], array('double', 'tinyint', 'int', 'varchar')) && array_key_exists('value', $field_desc) && $field_desc['value']) {
1021 $sql .= "(".$this->sanitize($field_desc['value']).")";
1022 }
1023 if (isset($field_desc['null']) && ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')) {
1024 // 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
1025 if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text') {
1026 $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";
1027 $this->query($sqlbis);
1028 } elseif (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1029 $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";
1030 $this->query($sqlbis);
1031 }
1032
1033 $sql .= " NOT NULL";
1034 }
1035
1036 if (isset($field_desc['default']) && $field_desc['default'] != '') {
1037 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1038 $sql .= " DEFAULT ".((float) $field_desc['default']);
1039 } elseif ($field_desc['type'] != 'text') {
1040 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'"; // Default not supported on text fields
1041 }
1042 }
1043
1044 dol_syslog(get_class($this)."::DDLUpdateField ".$sql, LOG_DEBUG);
1045 if (!$this->query($sql)) {
1046 return -1;
1047 } else {
1048 return 1;
1049 }
1050 }
1051
1052 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1060 public function DDLDropField($table, $field_name)
1061 {
1062 // phpcs:enable
1063 $tmp_field_name = preg_replace('/[^a-z0-9\.\-\_]/i', '', $field_name);
1064
1065 $sql = "ALTER TABLE ".$this->sanitize($table)." DROP COLUMN `".$this->sanitize($tmp_field_name)."`";
1066 if ($this->query($sql)) {
1067 return 1;
1068 }
1069 $this->error = $this->lasterror();
1070 return -1;
1071 }
1072
1073
1074 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1084 public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
1085 {
1086 // phpcs:enable
1087 $sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
1088 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1089 $resql = $this->query($sql);
1090 if (!$resql) {
1091 if ($this->lasterrno != 'DB_ERROR_USER_ALREADY_EXISTS') {
1092 return -1;
1093 } else {
1094 // If user already exists, we continue to set permissions
1095 dol_syslog(get_class($this)."::DDLCreateUser sql=".$sql, LOG_WARNING);
1096 }
1097 }
1098
1099 // Redo with localhost forced (sometimes user is created on %)
1100 $sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."'@'localhost' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
1101 $resql = $this->query($sql);
1102
1103 $sql = "GRANT ALL PRIVILEGES ON ".$this->escape($dolibarr_main_db_name).".* TO '".$this->escape($dolibarr_main_db_user)."'@'".$this->escape($dolibarr_main_db_host)."'";
1104 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1105 $resql = $this->query($sql);
1106 if (!$resql) {
1107 $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)."'";
1108 return -1;
1109 }
1110
1111 $sql = "FLUSH Privileges";
1112
1113 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG);
1114 $resql = $this->query($sql);
1115 if (!$resql) {
1116 return -1;
1117 }
1118
1119 return 1;
1120 }
1121
1129 public function getDefaultCharacterSetDatabase()
1130 {
1131 $resql = $this->query("SHOW VARIABLES LIKE 'character_set_database'");
1132 if (!$resql) {
1133 // version Mysql < 4.1.1
1134 return $this->forcecharset;
1135 }
1136 $liste = $this->fetch_array($resql);
1137 $tmpval = $liste['Value'];
1138
1139 return $tmpval;
1140 }
1141
1147 public function getListOfCharacterSet()
1148 {
1149 $resql = $this->query('SHOW CHARSET');
1150 $liste = array();
1151 if ($resql) {
1152 $i = 0;
1153 while ($obj = $this->fetch_object($resql)) {
1154 $liste[$i]['charset'] = $obj->Charset;
1155 $liste[$i]['description'] = $obj->Description;
1156 $i++;
1157 }
1158 $this->free($resql);
1159 } else {
1160 // version Mysql < 4.1.1
1161 return null;
1162 }
1163 return $liste;
1164 }
1165
1172 public function getDefaultCollationDatabase()
1173 {
1174 $resql = $this->query("SHOW VARIABLES LIKE 'collation_database'");
1175 if (!$resql) {
1176 // version Mysql < 4.1.1
1177 return $this->forcecollate;
1178 }
1179 $liste = $this->fetch_array($resql);
1180 $tmpval = $liste['Value'];
1181
1182 return $tmpval;
1183 }
1184
1190 public function getListOfCollation()
1191 {
1192 $resql = $this->query('SHOW COLLATION');
1193 $liste = array();
1194 if ($resql) {
1195 $i = 0;
1196 while ($obj = $this->fetch_object($resql)) {
1197 $liste[$i]['collation'] = $obj->Collation;
1198 $i++;
1199 }
1200 $this->free($resql);
1201 } else {
1202 // version Mysql < 4.1.1
1203 return null;
1204 }
1205 return $liste;
1206 }
1207
1213 public function getPathOfDump()
1214 {
1215 $fullpathofdump = '/pathtomysqldump/mysqldump';
1216
1217 $resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
1218 if ($resql) {
1219 $liste = $this->fetch_array($resql);
1220 $basedir = $liste['Value'];
1221 $fullpathofdump = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysqldump';
1222 }
1223 return $fullpathofdump;
1224 }
1225
1231 public function getPathOfRestore()
1232 {
1233 $fullpathofimport = '/pathtomysql/mysql';
1234
1235 $resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
1236 if ($resql) {
1237 $liste = $this->fetch_array($resql);
1238 $basedir = $liste['Value'];
1239 $fullpathofimport = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysql';
1240 }
1241 return $fullpathofimport;
1242 }
1243
1250 public function getServerParametersValues($filter = '')
1251 {
1252 $result = array();
1253
1254 $sql = 'SHOW VARIABLES';
1255 if ($filter) {
1256 $sql .= " LIKE '".$this->escape($filter)."'";
1257 }
1258 $resql = $this->query($sql);
1259 if ($resql) {
1260 while ($obj = $this->fetch_object($resql)) {
1261 $result[$obj->Variable_name] = $obj->Value;
1262 }
1263 }
1264
1265 return $result;
1266 }
1267
1274 public function getServerStatusValues($filter = '')
1275 {
1276 $result = array();
1277
1278 $sql = 'SHOW STATUS';
1279 if ($filter) {
1280 $sql .= " LIKE '".$this->escape($filter)."'";
1281 }
1282 $resql = $this->query($sql);
1283 if ($resql) {
1284 while ($obj = $this->fetch_object($resql)) {
1285 $result[$obj->Variable_name] = $obj->Value;
1286 }
1287 }
1288
1289 return $result;
1290 }
1291}
1292
1293
1294if (class_exists('myslqi')) {
1298 class mysqliDoli extends mysqli
1299 {
1311 public function __construct($host, $user, $pass, $name, $port = 0, $socket = "")
1312 {
1313 $flags = 0;
1314 if (PHP_VERSION_ID >= 80100) {
1315 parent::__construct();
1316 } else {
1317 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
1318 parent::init();
1319 }
1320 if (strpos($host, 'ssl://') === 0) {
1321 $host = substr($host, 6);
1322 parent::options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, false);
1323 // Suppress false positive @phan-suppress-next-line PhanTypeMismatchArgumentInternalProbablyReal
1324 parent::ssl_set(null, null, "", null, null);
1325 $flags = MYSQLI_CLIENT_SSL;
1326 }
1327 parent::real_connect($host, $user, $pass, $name, $port, $socket, $flags);
1328 }
1329 }
1330}
Class to manage Dolibarr database access.
lastqueryerror()
Return last query in error.
lasterror()
Return last error label.
lasterrno()
Return last error code.
lastquery()
Return last request executed with query()
Class to manage Dolibarr database access for a MySQL database using the MySQLi extension.
fetch_array($resultset)
Return data as an array.
__construct($type, $host, $user, $pass, $name='', $port=0)
Constructor.
free($resultset=null)
Libere le dernier resultset utilise sur cette connection.
escapeforlike($stringtoencode)
Escape a string to insert data into a like.
num_rows($resultset)
Return number of lines for result of a SELECT.
const VERSIONMIN
Version min database.
$type
Database type.
error()
Return description of last error.
escape($stringtoencode)
Escape a string to insert data.
getVersion()
Return version of database server.
fetch_object($resultset)
Returns the current line (as an object) for the resultset cursor.
encrypt($fieldorvalue, $withQuotes=1)
Encrypt sensitive data in database Warning: This function includes the escape and add the SQL simple ...
convertSQLFromMysql($line, $type='ddl')
Convert a SQL request in Mysql syntax to native syntax.
affected_rows($resultset)
Return the number of lines in the result of a request INSERT, DELETE or UPDATE.
select_db($database)
Select a database.
decrypt($value)
Decrypt sensitive data in database.
fetch_row($resultset)
Return data as an array.
last_insert_id($tab, $fieldid='rowid')
Get last ID after an insert INSERT.
const LABEL
Database label.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
hintindex($nameofindex)
Return SQL string to force an index.
connect($host, $login, $passwd, $name, $port=0)
Connect to server.
errno()
Return generic error code of last operation.
static getCallerInfoString()
Get caller info.
getDriverInfo()
Return version of database client driver.
close()
Close database connection.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:137