dolibarr 24.0.0-beta
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-2026 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2024 Charlene Benke <charlene@patas-monkey.com>
10 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 */
25
31require_once DOL_DOCUMENT_ROOT.'/core/db/DoliDB.class.php';
32
36class DoliDBMysqli extends DoliDB
37{
39 public $db;
41 public $type = 'mysqli';
42
44 const LABEL = 'MySQL or MariaDB';
46 const VERSIONMIN = '5.0.3';
47
49 private $_results;
50
62 public function __construct($type, $host, $user, $pass, $name = '', $port = 0) // @phpstan-ignore constructor.unusedParameter
63 {
64 global $conf, $langs;
65
66 // Note that having "static" property for "$forcecharset" and "$forcecollate" will make error here in strict mode, so they are not static
67 if (!empty($conf->db->character_set)) {
68 $this->forcecharset = $conf->db->character_set;
69 }
70 if (!empty($conf->db->dolibarr_main_db_collation)) {
71 $this->forcecollate = $conf->db->dolibarr_main_db_collation;
72 }
73
74 $this->database_user = $user;
75 $this->database_host = $host;
76 $this->database_port = $port;
77
78 $this->transaction_opened = 0;
79
80 //print "Name DB: $host,$user,$pass,$name<br>";
81
82 if (!class_exists('mysqli')) {
83 $this->connected = false;
84 $this->ok = false;
85 $this->error = "Mysqli PHP functions for using Mysqli driver are not available in this version of PHP. Try to use another driver.";
86 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);
87 }
88
89 if (!$host) {
90 $this->connected = false;
91 $this->ok = false;
92 $this->error = $langs->trans("ErrorWrongHostParameter");
93 dol_syslog(get_class($this)."::DoliDBMysqli : Connect error, wrong host parameters", LOG_ERR);
94 }
95
96 // Try server connection
97 // We do not try to connect to database, only to server. Connect to database is done later in constructor
98 $this->db = $this->connect($host, $user, $pass, '', $port);
99
100 if ($this->db && empty($this->db->connect_errno)) {
101 $this->connected = true;
102 $this->ok = true;
103 } else {
104 $this->connected = false;
105 $this->ok = false;
106 $this->error = empty($this->db) ? 'Failed to connect' : $this->db->connect_error;
107 dol_syslog(get_class($this)."::DoliDBMysqli Connect error: ".$this->error, LOG_ERR);
108 }
109
110 $disableforcecharset = 0; // Set to 1 to test without charset forcing
111
112 // If server connection is ok, we try to connect to the database
113 if ($this->connected && $name) {
114 if ($this->select_db($name)) {
115 $this->database_selected = true;
116 $this->database_name = $name;
117 $this->ok = true;
118
119 // If client is old latin, we force utf8
120 $clientmustbe = empty($conf->db->character_set) ? 'utf8' : (string) $conf->db->character_set;
121 if (preg_match('/latin1/', $clientmustbe)) {
122 $clientmustbe = 'utf8';
123 }
124
125 if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
126 try {
127 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);
128 // To get current charset: USE databasename; SHOW VARIABLES LIKE 'character_set_database'
129 // or: USE databasename; SELECT schema_name, default_character_set_name FROM information_schema.SCHEMATA;
130 // To get current collation: USE databasename; SHOW VARIABLES LIKE 'collation_database'
131 // or: USE databasename; SELECT schema_name, default_character_set_name FROM information_schema.SCHEMATA;
132 // To upgrade database default, you can do: ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
133
134 $this->db->set_charset($clientmustbe); // This set charset, but with a bad collation (colllation is forced later)
135 } catch (Exception $e) {
136 print 'Failed to force character_set_client to '.$clientmustbe." (according to setup) to match the one of the server database.<br>\n";
137 print $e->getMessage();
138 print "<br>\n";
139 if ($clientmustbe != 'utf8') {
140 print 'Edit conf/conf.php file to set a charset "utf8"';
141 if ($clientmustbe != 'utf8mb4') {
142 print ' or "utf8mb4"';
143 }
144 print ' instead of "'.$clientmustbe.'".'."\n";
145 }
146 exit;
147 }
148
149 $collation = (empty($conf) ? 'utf8_unicode_ci' : (string) $conf->db->dolibarr_main_db_collation);
150 if (preg_match('/latin1/', $collation)) {
151 $collation = 'utf8_unicode_ci';
152 }
153
154 if (!preg_match('/general/', $collation)) {
155 $this->db->query("SET collation_connection = ".$collation);
156 }
157 }
158 } else {
159 $this->database_selected = false;
160 $this->database_name = '';
161 $this->ok = false;
162 $this->error = $this->error();
163 dol_syslog(get_class($this)."::DoliDBMysqli : Select_db error ".$this->error, LOG_ERR);
164 }
165 } else {
166 // No selection of database done. We may only be connected or not (ok or ko) to the server.
167 $this->database_selected = false;
168
169 if ($this->connected) {
170 // If client is old latin, we force utf8
171 $clientmustbe = empty($conf->db->character_set) ? 'utf8' : (string) $conf->db->character_set;
172 if (preg_match('/latin1/', $clientmustbe)) {
173 $clientmustbe = 'utf8';
174 }
175 if (preg_match('/utf8mb4/', $clientmustbe)) {
176 $clientmustbe = 'utf8';
177 }
178
179 if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
180 $this->db->set_charset($clientmustbe); // This set utf8_unicode_ci
181
182 $collation = (string) $conf->db->dolibarr_main_db_collation;
183 if (preg_match('/latin1/', $collation)) {
184 $collation = 'utf8_unicode_ci';
185 }
186 if (preg_match('/utf8mb4/', $collation)) {
187 $collation = 'utf8_unicode_ci';
188 }
189
190 if (!preg_match('/general/', $collation)) {
191 $this->db->query("SET collation_connection = ".$collation);
192 }
193 }
194 }
195 }
196 }
197
198
206 public function hintindex($nameofindex, $mode = 1)
207 {
208 return " ".($mode == 1 ? 'FORCE' : 'USE')." INDEX(".preg_replace('/[^a-z0-9_]/', '', $nameofindex).")";
209 }
210
211
219 public function convertSQLFromMysql($line, $type = 'ddl')
220 {
221 return $line;
222 }
223
224
225 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
232 public function select_db($database)
233 {
234 // phpcs:enable
235 dol_syslog(get_class($this)."::select_db database=".$database, LOG_DEBUG);
236 $result = false;
237 try {
238 $result = $this->db->select_db($database);
239 } catch (Exception $e) {
240 // Nothing done on error
241 }
242 return $result;
243 }
244
245
257 public function connect($host, $login, $passwd, $name, $port = 0)
258 {
259 dol_syslog(get_class($this)."::connect host=$host, port=$port, login=$login, passwd=--hidden--, name=$name", LOG_DEBUG);
260
261 //mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
262
263 $tmp = false;
264 try {
265 if (!class_exists('mysqli')) {
266 dol_print_error(null, 'Driver mysqli for PHP not available');
267 return false;
268 }
269 if (strpos($host, 'ssl://') === 0) {
270 $tmp = new mysqliDoli($host, $login, $passwd, $name, $port);
271 } else {
272 $tmp = new mysqli($host, $login, $passwd, $name, $port);
273 }
274 } catch (Exception $e) {
275 dol_syslog(get_class($this)."::connect failed", LOG_DEBUG);
276 }
277 return $tmp;
278 }
279
285 public function getVersion()
286 {
287 return $this->db->server_info;
288 }
289
295 public function getDriverInfo()
296 {
297 return $this->db->client_info;
298 }
299
300
307 public function close()
308 {
309 if ($this->db) {
310 if ($this->transaction_opened > 0) {
311 dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR);
312 }
313 $this->connected = false;
314 return $this->db->close();
315 }
316 return false;
317 }
318
319
320
331 public function query($query, $usesavepoint = 0, $type = 'auto', $result_mode = 0)
332 {
333 global $dolibarr_main_db_readonly;
334
335 $query = trim($query);
336
337
338 /*if ($usesavepoint && $this->transaction_opened) {
339 dol_syslog(get_class($this)."::query SAVEPOINT mysavepoint", LOG_DEBUG); // Log of request was not yet done previously
340 $this->db->query('SAVEPOINT mysavepoint');
341 }*/
342
343 if (!in_array($query, array('BEGIN', 'COMMIT', 'ROLLBACK'))) {
344 $SYSLOG_SQL_LIMIT = 10000; // limit log to 10kb per line to limit DOS attacks
345 dol_syslog('sql='.substr($query, 0, $SYSLOG_SQL_LIMIT), LOG_DEBUG);
346 }
347 if (empty($query)) {
348 return false; // Return false = error if empty request
349 }
350
351 if (!empty($dolibarr_main_db_readonly)) {
352 if (preg_match('/^(INSERT|UPDATE|REPLACE|DELETE|CREATE|ALTER|TRUNCATE|DROP)/i', $query)) {
353 $this->lasterror = 'Application in read-only mode';
354 $this->lasterrno = 'APPREADONLY';
355 $this->lastquery = $query;
356 return false;
357 }
358 }
359
360 try {
361 $ret = $this->db->query($query, $result_mode);
362 } catch (Exception $e) {
363 dol_syslog(get_class($this)."::query Exception in query instead of returning an error: ".$e->getMessage(), LOG_ERR);
364 $ret = false;
365 }
366
367 if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) {
368 // Si requete utilisateur, on la sauvegarde ainsi que son resultset
369 if (!$ret) {
370 $this->lastqueryerror = $query;
371 $this->lasterror = $this->error();
372 $this->lasterrno = $this->errno();
373
374 if (getDolGlobalInt('SYSLOG_LEVEL') < LOG_DEBUG) {
375 dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously
376 }
377 dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterrno." ".$this->lasterror.self::getCallerInfoString(), LOG_ERR);
378 //var_dump(debug_print_backtrace());
379 }
380
381 /*if ($usesavepoint && $this->transaction_opened) { // Warning, after that errno will be erased
382 dol_syslog(get_class($this)."::query ROLLBACK TO SAVEPOINT mysavepoint", LOG_DEBUG); // Log of request was not yet done previously
383 $this->db->query('ROLLBACK TO SAVEPOINT mysavepoint');
384 }*/
385
386 $this->lastquery = $query;
387 $this->_results = $ret;
388 }
389
390 return $ret;
391 }
392
398 final protected static function getCallerInfoString()
399 {
400 $backtrace = debug_backtrace();
401 $msg = "";
402 if (count($backtrace) >= 1) {
403 $trace = $backtrace[1];
404 if (isset($trace['file'], $trace['line'])) {
405 $msg = " From {$trace['file']}:{$trace['line']}.";
406 }
407 }
408 return $msg;
409 }
410
411 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
418 public function fetch_object($resultset)
419 {
420 // phpcs:enable
421 // If the resultset was not provided, we get the last one for this connection
422 if (!is_object($resultset)) {
423 $resultset = $this->_results;
424 }
425 return $resultset->fetch_object();
426 }
427
428
429 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
436 public function fetch_array($resultset)
437 {
438 // phpcs:enable
439 // If resultset not provided, we take the last used by connection
440 if (!is_object($resultset)) {
441 $resultset = $this->_results;
442 }
443 return $resultset->fetch_array();
444 }
445
446 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
453 public function fetch_row($resultset)
454 {
455 // phpcs:enable
456 // If resultset not provided, we take the last used by connection
457 if (!is_bool($resultset)) {
458 if (!is_object($resultset)) {
459 $resultset = $this->_results;
460 }
461 return $resultset->fetch_row();
462 } else {
463 // If the cursor is a boolean, return 0
464 return 0;
465 }
466 }
467
468 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
476 public function num_rows($resultset)
477 {
478 // phpcs:enable
479 // If resultset not provided, we take the last used by connection
480 if (!is_object($resultset)) {
481 $resultset = $this->_results;
482 }
483 return isset($resultset->num_rows) ? $resultset->num_rows : 0;
484 }
485
486 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
494 public function affected_rows($resultset)
495 {
496 // phpcs:enable
497 // If resultset not provided, we take the last used by connection
498 if (!is_object($resultset)) {
499 $resultset = $this->_results;
500 }
501 // mysql necessite un link de base pour cette fonction contrairement
502 // a pqsql qui prend un resultset
503 return $this->db->affected_rows;
504 }
505
512 public function free($resultset = null)
513 {
514 // If resultset not provided, we take the last used by connection
515 if (!is_object($resultset)) {
516 $resultset = $this->_results;
517 }
518 // Si resultset en est un, on libere la memoire
519 if (is_object($resultset)) {
520 $resultset->free_result();
521 }
522 }
523
530 public function escape($stringtoencode)
531 {
532 return $this->db->real_escape_string((string) $stringtoencode);
533 }
534
541 public function escapeforlike($stringtoencode)
542 {
543 // We must first replace the \ char into \\, then we can replace _ and % into \_ and \%
544 return str_replace(array('\\', '_', '%'), array('\\\\', '\_', '\%'), (string) $stringtoencode);
545 }
546
552 public function errno()
553 {
554 if (!$this->connected) {
555 // Si il y a eu echec de connection, $this->db n'est pas valide.
556 return 'DB_ERROR_FAILED_TO_CONNECT';
557 } else {
558 // Constants to convert a MySql error code to a generic Dolibarr error code
559 $errorcode_map = array(
560 1004 => 'DB_ERROR_CANNOT_CREATE',
561 1005 => 'DB_ERROR_CANNOT_CREATE',
562 1006 => 'DB_ERROR_CANNOT_CREATE',
563 1007 => 'DB_ERROR_ALREADY_EXISTS',
564 1008 => 'DB_ERROR_CANNOT_DROP',
565 1022 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
566 1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
567 1044 => 'DB_ERROR_ACCESSDENIED',
568 1046 => 'DB_ERROR_NODBSELECTED',
569 1048 => 'DB_ERROR_CONSTRAINT',
570 1050 => 'DB_ERROR_TABLE_ALREADY_EXISTS',
571 1051 => 'DB_ERROR_NOSUCHTABLE',
572 1054 => 'DB_ERROR_NOSUCHFIELD',
573 1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
574 1061 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
575 1062 => 'DB_ERROR_RECORD_ALREADY_EXISTS',
576 1064 => 'DB_ERROR_SYNTAX',
577 1068 => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
578 1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
579 1091 => 'DB_ERROR_NOSUCHFIELD',
580 1100 => 'DB_ERROR_NOT_LOCKED',
581 1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
582 1146 => 'DB_ERROR_NOSUCHTABLE',
583 1215 => 'DB_ERROR_CANNOT_ADD_FOREIGN_KEY_CONSTRAINT',
584 1216 => 'DB_ERROR_NO_PARENT',
585 1217 => 'DB_ERROR_CHILD_EXISTS',
586 1396 => 'DB_ERROR_USER_ALREADY_EXISTS', // When creating a user that already existing
587 1451 => 'DB_ERROR_CHILD_EXISTS',
588 1826 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS'
589 );
590
591 if (isset($errorcode_map[$this->db->errno])) {
592 return $errorcode_map[$this->db->errno];
593 }
594 $errno = $this->db->errno;
595 return ($errno ? 'DB_ERROR_'.$errno : '0');
596 }
597 }
598
604 public function error()
605 {
606 if (!$this->connected) {
607 // Si il y a eu echec de connection, $this->db n'est pas valide pour mysqli_error.
608 return 'Not connected. Check setup parameters in conf/conf.php file and your mysql client and server versions';
609 } else {
610 return $this->db->error;
611 }
612 }
613
614 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
622 public function last_insert_id($tab, $fieldid = 'rowid')
623 {
624 // phpcs:enable
625 return $this->db->insert_id;
626 }
627
636 public function encrypt($fieldorvalue, $withQuotes = 1)
637 {
638 global $conf;
639
640 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
641 $cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);
642
643 //Encryption key
644 $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
645
646 $escapedstringwithquotes = ($withQuotes ? "'" : "").$this->escape($fieldorvalue).($withQuotes ? "'" : "");
647
648 if ($cryptType && !empty($cryptKey)) {
649 if ($cryptType == 2) {
650 $escapedstringwithquotes = "AES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
651 } elseif ($cryptType == 1) {
652 $escapedstringwithquotes = "DES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
653 }
654 }
655
656 return $escapedstringwithquotes;
657 }
658
665 public function decrypt($value)
666 {
667 global $conf;
668
669 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
670 $cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);
671
672 //Encryption key
673 $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
674
675 $return = $value;
676
677 if ($cryptType && !empty($cryptKey)) {
678 if ($cryptType == 2) {
679 $return = 'AES_DECRYPT('.$value.',\''.$cryptKey.'\')';
680 } elseif ($cryptType == 1) {
681 $return = 'DES_DECRYPT('.$value.',\''.$cryptKey.'\')';
682 }
683 }
684
685 return $return;
686 }
687
688
689 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
695 public function DDLGetConnectId()
696 {
697 // phpcs:enable
698 $resql = $this->query('SELECT CONNECTION_ID()');
699 if ($resql) {
700 $row = $this->fetch_row($resql);
701 return $row[0];
702 } else {
703 return '?';
704 }
705 }
706
707 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
719 public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
720 {
721 // phpcs:enable
722 if (empty($charset)) {
723 $charset = $this->forcecharset;
724 }
725 if (empty($collation)) {
726 $collation = $this->forcecollate;
727 }
728
729 // ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci
730 $sql = "CREATE DATABASE `".$this->escape($database)."`";
731 $sql .= " DEFAULT CHARACTER SET `".$this->escape($charset)."` DEFAULT COLLATE `".$this->escape($collation)."`";
732
733 dol_syslog($sql, LOG_DEBUG);
734 $ret = $this->query($sql);
735 if (!$ret) {
736 // We try again for compatibility with Mysql < 4.1.1
737 $sql = "CREATE DATABASE `".$this->escape($database)."`";
738 dol_syslog($sql, LOG_DEBUG);
739 $ret = $this->query($sql);
740 }
741
742 return $ret;
743 }
744
745 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
753 public function DDLListTables($database, $table = '')
754 {
755 // phpcs:enable
756 $listtables = array();
757
758 $like = '';
759 if ($table) {
760 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
761
762 $like = "LIKE '".$this->escape($tmptable)."'";
763 }
764 $tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);
765
766 $sql = "SHOW TABLES FROM `".$tmpdatabase."` ".$like.";";
767 //print $sql;
768 $result = $this->query($sql);
769 if ($result) {
770 while ($row = $this->fetch_row($result)) {
771 $listtables[] = $row[0];
772 }
773 }
774 return $listtables;
775 }
776
777 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
785 public function DDLListTablesFull($database, $table = '')
786 {
787 // phpcs:enable
788 $listtables = array();
789
790 $like = '';
791 if ($table) {
792 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
793
794 $like = "LIKE '".$this->escape($tmptable)."'";
795 }
796 $tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);
797
798 $sql = "SHOW FULL TABLES FROM `".$tmpdatabase."` ".$like.";";
799
800 $result = $this->query($sql);
801 if ($result) {
802 while ($row = $this->fetch_row($result)) {
803 $listtables[] = $row;
804 }
805 }
806 return $listtables;
807 }
808
809 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
816 public function DDLInfoTable($table)
817 {
818 // phpcs:enable
819 $infotables = array();
820
821 $sanitizedtmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
822
823 $sql = "SHOW FULL COLUMNS FROM ".$sanitizedtmptable.";";
824
825 dol_syslog($sql, LOG_DEBUG);
826 $result = $this->query($sql);
827 if ($result) {
828 while ($row = $this->fetch_row($result)) {
829 $infotables[] = $row;
830 }
831 }
832 return $infotables;
833 }
834
835 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
848 public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
849 {
850 // phpcs:enable
851 // @TODO: $fulltext_keys parameter is unused
852
853 if (empty($type)) {
854 $type = 'InnoDB';
855 }
856
857 $pk = '';
858 $sqlk = array();
859 $sqluq = array();
860
861 // Keys found into the array $fields: type,value,attribute,null,default,extra
862 // ex. : $fields['rowid'] = array(
863 // 'type'=>'int' or 'integer',
864 // 'value'=>'11',
865 // 'null'=>'not null',
866 // 'extra'=> 'auto_increment'
867 // );
868 $sql = "CREATE TABLE ".$this->sanitize($table)."(";
869 $i = 0;
870 $sqlfields = array();
871 foreach ($fields as $field_name => $field_desc) {
872 $sqlfields[$i] = $this->sanitize($field_name)." ";
873 $sqlfields[$i] .= $this->sanitize($field_desc['type']);
874 if (isset($field_desc['value']) && $field_desc['value'] !== '') {
875 $sqlfields[$i] .= "(".$this->sanitize($field_desc['value']).")";
876 }
877 if (isset($field_desc['attribute']) && $field_desc['attribute'] !== '') {
878 $sqlfields[$i] .= " ".$this->sanitize($field_desc['attribute'], 0, 0, 1); // Allow space to accept attributes like "ON UPDATE CURRENT_TIMESTAMP"
879 }
880 if (isset($field_desc['default']) && $field_desc['default'] !== '') {
881 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
882 $sqlfields[$i] .= " DEFAULT ".((float) $field_desc['default']);
883 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
884 $sqlfields[$i] .= " DEFAULT ".$this->sanitize($field_desc['default']);
885 } else {
886 $sqlfields[$i] .= " DEFAULT '".$this->escape($field_desc['default'])."'";
887 }
888 }
889 if (isset($field_desc['null']) && $field_desc['null'] !== '') {
890 $sqlfields[$i] .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
891 }
892 if (isset($field_desc['extra']) && $field_desc['extra'] !== '') {
893 $sqlfields[$i] .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
894 }
895 if (!empty($primary_key) && $primary_key == $field_name) {
896 $sqlfields[$i] .= " AUTO_INCREMENT PRIMARY KEY"; // mysql instruction that will be converted by driver late
897 }
898 $i++;
899 }
900
901 if (is_array($unique_keys)) {
902 $i = 0;
903 foreach ($unique_keys as $key => $value) {
904 $sqluq[$i] = "UNIQUE KEY '".$this->sanitize($key)."' ('".$this->escape($value)."')";
905 $i++;
906 }
907 }
908 if (is_array($keys)) {
909 $i = 0;
910 foreach ($keys as $key => $value) {
911 $sqlk[$i] = "KEY ".$this->sanitize($key)." (".$value.")";
912 $i++;
913 }
914 }
915 $sql .= implode(', ', $sqlfields);
916 if (!is_array($unique_keys) && $unique_keys != "") {
917 $sql .= ",".implode(',', $sqluq);
918 }
919 if (is_array($keys)) {
920 $sql .= ",".implode(',', $sqlk);
921 }
922 $sql .= ")";
923 $sql .= " engine=".$this->sanitize($type);
924
925 if (!$this->query($sql)) {
926 return -1;
927 } else {
928 return 1;
929 }
930 }
931
932 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
939 public function DDLDropTable($table)
940 {
941 // phpcs:enable
942 $tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
943
944 $sql = "DROP TABLE ".$this->sanitize($tmptable);
945
946 if (!$this->query($sql)) {
947 return -1;
948 } else {
949 return 1;
950 }
951 }
952
953 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
961 public function DDLDescTable($table, $field = "")
962 {
963 // phpcs:enable
964 $sql = "DESC ".$this->sanitize($table)." ".$this->sanitize($field);
965
966 dol_syslog(get_class($this)."::DDLDescTable ".$sql, LOG_DEBUG);
967 $this->_results = $this->query($sql);
968 return $this->_results;
969 }
970
971 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
981 public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
982 {
983 // phpcs:enable
984 // cles recherchees dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra
985 // ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
986 $sql = "ALTER TABLE ".$this->sanitize($table)." ADD ".$this->sanitize($field_name)." ";
987
988 if ($field_desc['type'] !== 'datetimegmt') {
989 $sql .= $this->sanitize($field_desc['type']);
990 } else {
991 $sql .= 'datetime';
992 }
993
994 if (in_array($field_desc['type'], array('double', 'int', 'varchar')) && array_key_exists('value', $field_desc) && !empty($field_desc['value'])) {
995 $sql .= "(".$this->sanitize($field_desc['value']).")";
996 }
997 if (isset($field_desc['attribute']) && preg_match("/^[^\s]/i", $field_desc['attribute'])) {
998 $sql .= " ".$this->sanitize($field_desc['attribute']);
999 }
1000 if (isset($field_desc['null']) && preg_match("/^[^\s]/i", $field_desc['null'])) {
1001 if ($field_desc['null'] == 'NOT NULL') {
1002 $sql .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
1003 } else {
1004 $sql .= " ".$this->sanitize($field_desc['null']);
1005 }
1006 }
1007 if (isset($field_desc['default']) && preg_match("/^[^\s]/i", $field_desc['default'])) {
1008 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1009 $sql .= " DEFAULT ".((float) $field_desc['default']);
1010 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
1011 $sql .= " DEFAULT ".$this->sanitize($field_desc['default']);
1012 } else {
1013 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'";
1014 }
1015 }
1016 if (isset($field_desc['extra']) && preg_match("/^[^\s]/i", $field_desc['extra'])) {
1017 $sql .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
1018 }
1019 $sql .= " ".$this->sanitize($field_position, 0, 0, 1);
1020
1021 dol_syslog(get_class($this)."::DDLAddField ".$sql, LOG_DEBUG);
1022 if ($this->query($sql)) {
1023 return 1;
1024 }
1025 return -1;
1026 }
1027
1028 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1037 public function DDLUpdateField($table, $field_name, $field_desc)
1038 {
1039 // phpcs:enable
1040 $sql = "ALTER TABLE ".$this->sanitize($table);
1041 $sql .= " MODIFY COLUMN ".$this->sanitize($field_name)." ";
1042
1043 if ($field_desc['type'] !== 'datetimegmt') {
1044 $sql .= $this->sanitize($field_desc['type']);
1045 } else {
1046 $sql .= 'datetime';
1047 }
1048
1049 if (in_array($field_desc['type'], array('double', 'int', 'varchar')) && array_key_exists('value', $field_desc) && !empty($field_desc['value'])) {
1050 $sql .= "(".$this->sanitize($field_desc['value']).")";
1051 }
1052 if (isset($field_desc['null']) && ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')) {
1053 // 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
1054 if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text') {
1055 $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";
1056 $this->query($sqlbis);
1057 } elseif (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1058 $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";
1059 $this->query($sqlbis);
1060 }
1061
1062 $sql .= " NOT NULL";
1063 }
1064
1065 if (isset($field_desc['default']) && $field_desc['default'] != '') {
1066 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1067 $sql .= " DEFAULT ".((float) $field_desc['default']);
1068 } elseif ($field_desc['type'] != 'text') {
1069 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'"; // Default not supported on text fields
1070 }
1071 }
1072
1073 //print $sql;exit;
1074 dol_syslog(get_class($this)."::DDLUpdateField ".$sql, LOG_DEBUG);
1075 if (!$this->query($sql)) {
1076 return -1;
1077 } else {
1078 return 1;
1079 }
1080 }
1081
1082 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1090 public function DDLDropField($table, $field_name)
1091 {
1092 // phpcs:enable
1093 $tmp_field_name = preg_replace('/[^a-z0-9\.\-\_]/i', '', $field_name);
1094
1095 $sql = "ALTER TABLE ".$this->sanitize($table)." DROP COLUMN `".$this->sanitize($tmp_field_name)."`";
1096 if ($this->query($sql)) {
1097 return 1;
1098 }
1099 $this->error = $this->lasterror();
1100 return -1;
1101 }
1102
1103
1104 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1114 public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
1115 {
1116 // phpcs:enable
1117 $sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
1118 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1119 $resql = $this->query($sql);
1120 if (!$resql) {
1121 if ($this->lasterrno != 'DB_ERROR_USER_ALREADY_EXISTS') {
1122 return -1;
1123 } else {
1124 // If user already exists, we continue to set permissions
1125 dol_syslog(get_class($this)."::DDLCreateUser sql=".$sql, LOG_WARNING);
1126 }
1127 }
1128
1129 // Redo with localhost forced (sometimes user is created on %)
1130 $sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."'@'localhost' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
1131 $resql = $this->query($sql);
1132
1133 $sql = "GRANT ALL PRIVILEGES ON ".$this->escape($dolibarr_main_db_name).".* TO '".$this->escape($dolibarr_main_db_user)."'@'".$this->escape($dolibarr_main_db_host)."'";
1134 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1135 $resql = $this->query($sql);
1136 if (!$resql) {
1137 $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)."'";
1138 return -1;
1139 }
1140
1141 $sql = "FLUSH Privileges";
1142
1143 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG);
1144 $resql = $this->query($sql);
1145 if (!$resql) {
1146 return -1;
1147 }
1148
1149 return 1;
1150 }
1151
1159 public function getDefaultCharacterSetDatabase()
1160 {
1161 $resql = $this->query("SHOW VARIABLES LIKE 'character_set_database'");
1162 if (!$resql) {
1163 // version Mysql < 4.1.1
1164 return $this->forcecharset;
1165 }
1166 $liste = $this->fetch_array($resql);
1167 $tmpval = $liste['Value'];
1168
1169 return $tmpval;
1170 }
1171
1177 public function getListOfCharacterSet()
1178 {
1179 $resql = $this->query('SHOW CHARSET');
1180 $liste = array();
1181 if ($resql) {
1182 $i = 0;
1183 while ($obj = $this->fetch_object($resql)) {
1184 $liste[$i]['charset'] = $obj->Charset;
1185 $liste[$i]['description'] = $obj->Description;
1186 $i++;
1187 }
1188 $this->free($resql);
1189 } else {
1190 // version Mysql < 4.1.1
1191 return null;
1192 }
1193 return $liste;
1194 }
1195
1202 public function getDefaultCollationDatabase()
1203 {
1204 $resql = $this->query("SHOW VARIABLES LIKE 'collation_database'");
1205 if (!$resql) {
1206 // version Mysql < 4.1.1
1207 return $this->forcecollate;
1208 }
1209 $liste = $this->fetch_array($resql);
1210 $tmpval = $liste['Value'];
1211
1212 return $tmpval;
1213 }
1214
1220 public function getListOfCollation()
1221 {
1222 $resql = $this->query('SHOW COLLATION');
1223 $liste = array();
1224 if ($resql) {
1225 $i = 0;
1226 while ($obj = $this->fetch_object($resql)) {
1227 $liste[$i]['collation'] = $obj->Collation;
1228 $i++;
1229 }
1230 $this->free($resql);
1231 } else {
1232 // version Mysql < 4.1.1
1233 return null;
1234 }
1235 return $liste;
1236 }
1237
1243 public function getPathOfDump()
1244 {
1245 $fullpathofdump = '/pathtomysqldump/mysqldump';
1246
1247 $resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
1248 if ($resql) {
1249 $liste = $this->fetch_array($resql);
1250 $basedir = $liste['Value'];
1251 $fullpathofdump = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysqldump';
1252 }
1253 return $fullpathofdump;
1254 }
1255
1261 public function getPathOfRestore()
1262 {
1263 $fullpathofimport = '/pathtomysql/mysql';
1264
1265 $resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
1266 if ($resql) {
1267 $liste = $this->fetch_array($resql);
1268 $basedir = $liste['Value'];
1269 $fullpathofimport = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysql';
1270 }
1271 return $fullpathofimport;
1272 }
1273
1280 public function getServerParametersValues($filter = '')
1281 {
1282 $result = array();
1283
1284 $sql = 'SHOW VARIABLES';
1285 if ($filter) {
1286 $sql .= " LIKE '".$this->escape($filter)."'";
1287 }
1288 $resql = $this->query($sql);
1289 if ($resql) {
1290 while ($obj = $this->fetch_object($resql)) {
1291 $result[$obj->Variable_name] = $obj->Value;
1292 }
1293 }
1294
1295 return $result;
1296 }
1297
1304 public function getServerStatusValues($filter = '')
1305 {
1306 $result = array();
1307
1308 $sql = 'SHOW STATUS';
1309 if ($filter) {
1310 $sql .= " LIKE '".$this->escape($filter)."'";
1311 }
1312 $resql = $this->query($sql);
1313 if ($resql) {
1314 while ($obj = $this->fetch_object($resql)) {
1315 $result[$obj->Variable_name] = $obj->Value;
1316 }
1317 }
1318
1319 return $result;
1320 }
1321
1328 public function getNextAutoIncrementId($table)
1329 {
1330 // Request to get last status of table
1331 $sql = "SHOW TABLE STATUS LIKE '".$this->escape($table)."'";
1332 $result = $this->query($sql);
1333
1334 if ($result) {
1335 $obj = $this->fetch_object($result);
1336 if ($obj && isset($obj->Auto_increment)) {
1337 return (int) $obj->Auto_increment;
1338 }
1339 }
1340
1341 return -1;
1342 }
1343
1350 public function prepare($sql)
1351 {
1352 if (!$this->connected) {
1353 $this->lasterror = 'Not connected to database';
1354 return false;
1355 }
1356 $stmt = $this->db->prepare($sql);
1357 if ($stmt === false) {
1358 $this->lasterror = $this->db->error;
1359 $this->lastqueryerror = $sql;
1360 return false;
1361 }
1362
1363 return $stmt;
1364 }
1365}
1366
1367if (class_exists('mysqli')) {
1371 class mysqliDoli extends mysqli
1372 {
1384 public function __construct($host, $user, $pass, $name, $port = 0, $socket = "") // @phpstan-ignore constructor.unusedParameter
1385 {
1386 $flags = 0;
1387 if (PHP_VERSION_ID >= 80100) {
1388 parent::__construct();
1389 } else {
1390 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
1391 parent::init();
1392 }
1393 if (strpos($host, 'ssl://') === 0) {
1394 $host = substr($host, 6);
1395 parent::options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, 0);
1396 // Suppress false positive @phan-suppress-next-line PhanTypeMismatchArgumentInternalProbablyReal
1397 parent::ssl_set(null, null, "", null, null);
1398 $flags = MYSQLI_CLIENT_SSL;
1399 }
1400 parent::real_connect($host, $user, $pass, $name, $port, $socket, $flags);
1401 }
1402 }
1403}
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)
Free the last pointer resultset used by this connection.
escapeforlike($stringtoencode)
Escape a string to insert data into a like.
num_rows($resultset)
Return number of lines for result of a SELECT.
hintindex($nameofindex, $mode=1)
Return SQL string to force an index.
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.
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.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:130