dolibarr 25.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-2026 MDW <mdeweerd@users.noreply.github.com>
9 * Copyright (C) 2024 Charlene Benke <charlene@patas-monkey.com>
10 * Copyright (C) 2024-2026 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 (Throwable $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
176 if (empty($disableforcecharset) && $this->db->character_set_name() != $clientmustbe) {
177 $this->db->set_charset($clientmustbe); // This set utf8_unicode_ci or utf8mb4_unicode_ci
178
179 $collation = (string) $conf->db->dolibarr_main_db_collation;
180 if (preg_match('/latin1/', $collation)) {
181 $collation = 'utf8_unicode_ci';
182 }
183
184 if (!preg_match('/general/', $collation)) {
185 $this->db->query("SET collation_connection = ".$collation);
186 }
187 }
188 }
189 }
190 }
191
192
200 public function hintindex($nameofindex, $mode = 1)
201 {
202 return " ".($mode == 1 ? 'FORCE' : 'USE')." INDEX(".preg_replace('/[^a-z0-9_]/', '', $nameofindex).")";
203 }
204
205
213 public function convertSQLFromMysql($line, $type = 'ddl')
214 {
215 return $line;
216 }
217
218
219 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
226 public function select_db($database)
227 {
228 // phpcs:enable
229 dol_syslog(get_class($this)."::select_db database=".$database, LOG_DEBUG);
230 $result = false;
231 try {
232 $result = $this->db->select_db($database);
233 } catch (Throwable $e) {
234 // Nothing done on error
235 }
236 return $result;
237 }
238
239
251 public function connect($host, $login, $passwd, $name, $port = 0)
252 {
253 dol_syslog(get_class($this)."::connect host=$host, port=$port, login=$login, passwd=--hidden--, name=$name", LOG_DEBUG);
254
255 //mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
256
257 $tmp = false;
258 try {
259 if (!class_exists('mysqli')) {
260 dol_print_error(null, 'Driver mysqli for PHP not available');
261 return false;
262 }
263 if (strpos($host, 'ssl://') === 0) {
264 $tmp = new mysqliDoli($host, $login, $passwd, $name, $port);
265 } else {
266 $tmp = new mysqli($host, $login, $passwd, $name, $port);
267 }
268 } catch (Throwable $e) {
269 dol_syslog(get_class($this)."::connect failed", LOG_DEBUG);
270 }
271 return $tmp;
272 }
273
279 public function getVersion()
280 {
281 return $this->db->server_info;
282 }
283
289 public function getDriverInfo()
290 {
291 return $this->db->client_info;
292 }
293
294
301 public function close()
302 {
303 if ($this->db) {
304 if ($this->transaction_opened > 0) {
305 dol_syslog(get_class($this)."::close Closing a connection with an opened transaction depth=".$this->transaction_opened, LOG_ERR);
306 }
307 $this->connected = false;
308 return $this->db->close();
309 }
310 return false;
311 }
312
313
314
325 public function query($query, $usesavepoint = 0, $type = 'auto', $result_mode = 0)
326 {
327 global $dolibarr_main_db_readonly;
328
329 $query = trim($query);
330
331
332 /*if ($usesavepoint && $this->transaction_opened) {
333 dol_syslog(get_class($this)."::query SAVEPOINT mysavepoint", LOG_DEBUG); // Log of request was not yet done previously
334 $this->db->query('SAVEPOINT mysavepoint');
335 }*/
336
337 if (!in_array($query, array('BEGIN', 'COMMIT', 'ROLLBACK'))) {
338 $SYSLOG_SQL_LIMIT = 10000; // limit log to 10kb per line to limit DOS attacks
339 dol_syslog('sql='.substr($query, 0, $SYSLOG_SQL_LIMIT), LOG_DEBUG);
340 }
341 if (empty($query)) {
342 return false; // Return false = error if empty request
343 }
344
345 if (!empty($dolibarr_main_db_readonly)) {
346 if (preg_match('/^(INSERT|UPDATE|REPLACE|DELETE|CREATE|ALTER|TRUNCATE|DROP)/i', $query)) {
347 $this->lasterror = 'Application in read-only mode';
348 $this->lasterrno = 'APPREADONLY';
349 $this->lastquery = $query;
350 return false;
351 }
352 }
353
354 try {
355 $ret = $this->db->query($query, $result_mode);
356 } catch (Throwable $e) {
357 dol_syslog(get_class($this)."::query Exception in query instead of returning an error: ".$e->getMessage(), LOG_ERR);
358 $ret = false;
359 }
360
361 if (!preg_match("/^COMMIT/i", $query) && !preg_match("/^ROLLBACK/i", $query)) {
362 // If user query, we save it along with its resultset
363 if (!$ret) {
364 $this->lastqueryerror = $query;
365 $this->lasterror = $this->error();
366 $this->lasterrno = $this->errno();
367
368 if (getDolGlobalInt('SYSLOG_LEVEL') < LOG_DEBUG) {
369 dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); // Log of request was not yet done previously
370 }
371 dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterrno." ".$this->lasterror.self::getCallerInfoString(), LOG_ERR);
372 //var_dump(debug_print_backtrace());
373 }
374
375 /*if ($usesavepoint && $this->transaction_opened) { // Warning, after that errno will be erased
376 dol_syslog(get_class($this)."::query ROLLBACK TO SAVEPOINT mysavepoint", LOG_DEBUG); // Log of request was not yet done previously
377 $this->db->query('ROLLBACK TO SAVEPOINT mysavepoint');
378 }*/
379
380 $this->lastquery = $query;
381 $this->_results = $ret;
382 }
383
384 return $ret;
385 }
386
392 final protected static function getCallerInfoString()
393 {
394 $backtrace = debug_backtrace();
395 $msg = "";
396 if (count($backtrace) >= 1) {
397 $trace = $backtrace[1];
398 if (isset($trace['file'], $trace['line'])) {
399 $msg = " From {$trace['file']}:{$trace['line']}.";
400 }
401 }
402 return $msg;
403 }
404
405 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
412 public function fetch_object($resultset)
413 {
414 // phpcs:enable
415 // If the resultset was not provided, we get the last one for this connection
416 if (!is_object($resultset)) {
417 $resultset = $this->_results;
418 }
419 return $resultset->fetch_object();
420 }
421
422
423 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
430 public function fetch_array($resultset)
431 {
432 // phpcs:enable
433 // If resultset not provided, we take the last used by connection
434 if (!is_object($resultset)) {
435 $resultset = $this->_results;
436 }
437 return $resultset->fetch_array();
438 }
439
440 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
447 public function fetch_row($resultset)
448 {
449 // phpcs:enable
450 // If resultset not provided, we take the last used by connection
451 if (!is_bool($resultset)) {
452 if (!is_object($resultset)) {
453 $resultset = $this->_results;
454 }
455 return $resultset->fetch_row();
456 } else {
457 // If the cursor is a boolean, return 0
458 return 0;
459 }
460 }
461
462 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
470 public function num_rows($resultset)
471 {
472 // phpcs:enable
473 // If resultset not provided, we take the last used by connection
474 if (!is_object($resultset)) {
475 $resultset = $this->_results;
476 }
477 return isset($resultset->num_rows) ? $resultset->num_rows : 0;
478 }
479
480 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
488 public function affected_rows($resultset)
489 {
490 // phpcs:enable
491 // If resultset not provided, we take the last used by connection
492 if (!is_object($resultset)) {
493 $resultset = $this->_results;
494 }
495 // mysql require a db link, not like pqsql that takes a resultset
496 return $this->db->affected_rows;
497 }
498
505 public function free($resultset = null)
506 {
507 // If resultset not provided, we take the last used by connection
508 if (!is_object($resultset)) {
509 $resultset = $this->_results;
510 }
511 // If resultset is provided, free memory
512 if (is_object($resultset)) {
513 $resultset->free_result();
514 }
515 }
516
523 public function escape($stringtoencode)
524 {
525 return $this->db->real_escape_string((string) $stringtoencode);
526 }
527
534 public function escapeforlike($stringtoencode)
535 {
536 // We must first replace the \ char into \\, then we can replace _ and % into \_ and \%
537 return str_replace(array('\\', '_', '%'), array('\\\\', '\_', '\%'), (string) $stringtoencode);
538 }
539
545 public function errno()
546 {
547 if (!$this->connected) {
548 // If the connection failed, $this->db is not valid.
549 return 'DB_ERROR_FAILED_TO_CONNECT';
550 } else {
551 // Constants to convert a MySql error code to a generic Dolibarr error code
552 $errorcode_map = array(
553 1004 => 'DB_ERROR_CANNOT_CREATE',
554 1005 => 'DB_ERROR_CANNOT_CREATE',
555 1006 => 'DB_ERROR_CANNOT_CREATE',
556 1007 => 'DB_ERROR_ALREADY_EXISTS',
557 1008 => 'DB_ERROR_CANNOT_DROP',
558 1022 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
559 1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
560 1044 => 'DB_ERROR_ACCESSDENIED',
561 1046 => 'DB_ERROR_NODBSELECTED',
562 1048 => 'DB_ERROR_CONSTRAINT',
563 1050 => 'DB_ERROR_TABLE_ALREADY_EXISTS',
564 1051 => 'DB_ERROR_NOSUCHTABLE',
565 1054 => 'DB_ERROR_NOSUCHFIELD',
566 1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
567 1061 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
568 1062 => 'DB_ERROR_RECORD_ALREADY_EXISTS',
569 1064 => 'DB_ERROR_SYNTAX',
570 1068 => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
571 1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
572 1091 => 'DB_ERROR_NOSUCHFIELD',
573 1100 => 'DB_ERROR_NOT_LOCKED',
574 1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
575 1146 => 'DB_ERROR_NOSUCHTABLE',
576 1215 => 'DB_ERROR_CANNOT_ADD_FOREIGN_KEY_CONSTRAINT',
577 1216 => 'DB_ERROR_NO_PARENT',
578 1217 => 'DB_ERROR_CHILD_EXISTS',
579 1396 => 'DB_ERROR_USER_ALREADY_EXISTS', // When creating a user that already existing
580 1451 => 'DB_ERROR_CHILD_EXISTS',
581 1824 => 'DB_ERROR_CANNOT_CREATE', // When creating a constraint on a parent table that does not exists
582 1826 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS'
583 );
584
585 if (isset($errorcode_map[$this->db->errno])) {
586 return $errorcode_map[$this->db->errno];
587 }
588 $errno = $this->db->errno;
589 return ($errno ? 'DB_ERROR_'.$errno : '0');
590 }
591 }
592
598 public function error()
599 {
600 if (!$this->connected) {
601 // When there is a connection failure, $this->db is invalid for to get mysqli_error.
602 return 'Not connected. Check setup parameters in conf/conf.php file and your mysql client and server versions';
603 } else {
604 return $this->db->error;
605 }
606 }
607
608 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
616 public function last_insert_id($tab, $fieldid = 'rowid')
617 {
618 // phpcs:enable
619 return $this->db->insert_id;
620 }
621
630 public function encrypt($fieldorvalue, $withQuotes = 1)
631 {
632 global $conf;
633
634 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
635 $cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);
636
637 //Encryption key
638 $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
639
640 $escapedstringwithquotes = ($withQuotes ? "'" : "").$this->escape($fieldorvalue).($withQuotes ? "'" : "");
641
642 if ($cryptType && !empty($cryptKey)) {
643 if ($cryptType == 2) {
644 $escapedstringwithquotes = "AES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
645 } elseif ($cryptType == 1) {
646 $escapedstringwithquotes = "DES_ENCRYPT(".$escapedstringwithquotes.", '".$this->escape($cryptKey)."')";
647 }
648 }
649
650 return $escapedstringwithquotes;
651 }
652
659 public function decrypt($value)
660 {
661 global $conf;
662
663 // Type of encryption (2: AES (recommended), 1: DES , 0: no encryption)
664 $cryptType = (!empty($conf->db->dolibarr_main_db_encryption) ? $conf->db->dolibarr_main_db_encryption : 0);
665
666 //Encryption key
667 $cryptKey = (!empty($conf->db->dolibarr_main_db_cryptkey) ? $conf->db->dolibarr_main_db_cryptkey : '');
668
669 $return = $value;
670
671 if ($cryptType && !empty($cryptKey)) {
672 if ($cryptType == 2) {
673 $return = 'AES_DECRYPT('.$value.',\''.$cryptKey.'\')';
674 } elseif ($cryptType == 1) {
675 $return = 'DES_DECRYPT('.$value.',\''.$cryptKey.'\')';
676 }
677 }
678
679 return $return;
680 }
681
682
683 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
689 public function DDLGetConnectId()
690 {
691 // phpcs:enable
692 $resql = $this->query('SELECT CONNECTION_ID()');
693 if ($resql) {
694 $row = $this->fetch_row($resql);
695 return $row[0];
696 } else {
697 return '?';
698 }
699 }
700
701 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
713 public function DDLCreateDb($database, $charset = '', $collation = '', $owner = '')
714 {
715 // phpcs:enable
716 if (empty($charset)) {
717 $charset = $this->forcecharset;
718 }
719 if (empty($collation)) {
720 $collation = $this->forcecollate;
721 }
722
723 // ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci
724 $sql = "CREATE DATABASE `".$this->sanitize($database)."`";
725 $sql .= " DEFAULT CHARACTER SET `".$this->sanitize($charset)."` DEFAULT COLLATE `".$this->sanitize($collation)."`";
726
727 dol_syslog($sql, LOG_DEBUG);
728 $ret = $this->query($sql);
729 if (!$ret) {
730 // We try again for compatibility with Mysql < 4.1.1
731 $sql = "CREATE DATABASE `".$this->sanitize($database)."`";
732 dol_syslog($sql, LOG_DEBUG);
733 $ret = $this->query($sql);
734 }
735
736 return $ret;
737 }
738
739 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
747 public function DDLListTables($database, $table = '')
748 {
749 // phpcs:enable
750 $listtables = array();
751
752 $like = '';
753 if ($table) {
754 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
755
756 $like = "LIKE '".$this->escape($tmptable)."'";
757 }
758 $tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);
759
760 $sql = "SHOW TABLES FROM `".$tmpdatabase."` ".$like.";";
761 //print $sql;
762 $result = $this->query($sql);
763 if ($result) {
764 while ($row = $this->fetch_row($result)) {
765 $listtables[] = $row[0];
766 }
767 }
768 return $listtables;
769 }
770
771 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
779 public function DDLListTablesFull($database, $table = '')
780 {
781 // phpcs:enable
782 $listtables = array();
783
784 $like = '';
785 if ($table) {
786 $tmptable = preg_replace('/[^a-z0-9\.\-\_%]/i', '', $table);
787
788 $like = "LIKE '".$this->escape($tmptable)."'";
789 }
790 $tmpdatabase = preg_replace('/[^a-z0-9\.\-\_]/i', '', $database);
791
792 $sql = "SHOW FULL TABLES FROM `".$tmpdatabase."` ".$like.";";
793
794 $result = $this->query($sql);
795 if ($result) {
796 while ($row = $this->fetch_row($result)) {
797 $listtables[] = $row;
798 }
799 }
800 return $listtables;
801 }
802
803 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
810 public function DDLInfoTable($table)
811 {
812 // phpcs:enable
813 $infotables = array();
814
815 $sanitizedtmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
816
817 $sql = "SHOW FULL COLUMNS FROM ".$sanitizedtmptable.";";
818
819 dol_syslog($sql, LOG_DEBUG);
820 $result = $this->query($sql);
821 if ($result) {
822 while ($row = $this->fetch_row($result)) {
823 $infotables[] = $row;
824 }
825 }
826 return $infotables;
827 }
828
829 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
842 public function DDLCreateTable($table, $fields, $primary_key, $type, $unique_keys = null, $fulltext_keys = null, $keys = null)
843 {
844 // phpcs:enable
845 // @TODO: $fulltext_keys parameter is unused
846
847 if (empty($type)) {
848 $type = 'InnoDB';
849 }
850
851 $pk = '';
852 $sqlk = array();
853 $sqluq = array();
854
855 // Keys found into the array $fields: type,value,attribute,null,default,extra
856 // ex. : $fields['rowid'] = array(
857 // 'type'=>'int' or 'integer',
858 // 'value'=>'11',
859 // 'null'=>'not null',
860 // 'extra'=> 'auto_increment'
861 // );
862 $sql = "CREATE TABLE ".$this->sanitize($table)."(";
863 $i = 0;
864 $sqlfields = array();
865 foreach ($fields as $field_name => $field_desc) {
866 $sqlfields[$i] = $this->sanitize($field_name)." ";
867 $sqlfields[$i] .= $this->sanitize($field_desc['type']);
868 if (isset($field_desc['value']) && $field_desc['value'] !== '') {
869 $sqlfields[$i] .= "(".$this->sanitize($field_desc['value']).")";
870 }
871 if (isset($field_desc['attribute']) && $field_desc['attribute'] !== '') {
872 $sqlfields[$i] .= " ".$this->sanitize($field_desc['attribute'], 0, 0, 1); // Allow space to accept attributes like "ON UPDATE CURRENT_TIMESTAMP"
873 }
874 if (isset($field_desc['default']) && $field_desc['default'] !== '') {
875 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
876 $sqlfields[$i] .= " DEFAULT ".((float) $field_desc['default']);
877 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
878 $sqlfields[$i] .= " DEFAULT ".$this->sanitize($field_desc['default']);
879 } else {
880 $sqlfields[$i] .= " DEFAULT '".$this->escape($field_desc['default'])."'";
881 }
882 }
883 if (isset($field_desc['null']) && $field_desc['null'] !== '') {
884 $sqlfields[$i] .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
885 }
886 if (isset($field_desc['extra']) && $field_desc['extra'] !== '') {
887 $sqlfields[$i] .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
888 }
889 if (!empty($primary_key) && $primary_key == $field_name) {
890 $sqlfields[$i] .= " AUTO_INCREMENT PRIMARY KEY"; // mysql instruction that will be converted by driver late
891 }
892 $i++;
893 }
894
895 if (is_array($unique_keys)) {
896 $i = 0;
897 foreach ($unique_keys as $key => $value) {
898 $sqluq[$i] = "UNIQUE KEY '".$this->sanitize($key)."' ('".$this->escape($value)."')";
899 $i++;
900 }
901 }
902 if (is_array($keys)) {
903 $i = 0;
904 foreach ($keys as $key => $value) {
905 $sqlk[$i] = "KEY ".$this->sanitize($key)." (".$value.")";
906 $i++;
907 }
908 }
909 $sql .= implode(', ', $sqlfields);
910 if (!is_array($unique_keys) && $unique_keys != "") {
911 $sql .= ",".implode(',', $sqluq);
912 }
913 if (is_array($keys)) {
914 $sql .= ",".implode(',', $sqlk);
915 }
916 $sql .= ")";
917 $sql .= " engine=".$this->sanitize($type);
918
919 if (!$this->query($sql)) {
920 return -1;
921 } else {
922 return 1;
923 }
924 }
925
926 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
933 public function DDLDropTable($table)
934 {
935 // phpcs:enable
936 $tmptable = preg_replace('/[^a-z0-9\.\-\_]/i', '', $table);
937
938 $sql = "DROP TABLE ".$this->sanitize($tmptable);
939
940 if (!$this->query($sql)) {
941 return -1;
942 } else {
943 return 1;
944 }
945 }
946
947 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
955 public function DDLDescTable($table, $field = "")
956 {
957 // phpcs:enable
958 $sql = "DESC ".$this->sanitize($table)." ".$this->sanitize($field);
959
960 dol_syslog(get_class($this)."::DDLDescTable ".$sql, LOG_DEBUG);
961 $this->_results = $this->query($sql);
962 return $this->_results;
963 }
964
965 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
975 public function DDLAddField($table, $field_name, $field_desc, $field_position = "")
976 {
977 // phpcs:enable
978 // keys looked up in the descriptions array (field_desc): type,value,attribute,null,default,extra
979 // ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
980 $sql = "ALTER TABLE ".$this->sanitize($table)." ADD ".$this->sanitize($field_name)." ";
981
982 if ($field_desc['type'] !== 'datetimegmt') {
983 $sql .= $this->sanitize($field_desc['type']);
984 } else {
985 $sql .= 'datetime';
986 }
987
988 if (in_array($field_desc['type'], array('double', 'int', 'varchar')) && array_key_exists('value', $field_desc) && !empty($field_desc['value'])) {
989 $sql .= "(".$this->sanitize($field_desc['value']).")";
990 }
991 if (isset($field_desc['attribute']) && preg_match("/^[^\s]/i", $field_desc['attribute'])) {
992 $sql .= " ".$this->sanitize($field_desc['attribute']);
993 }
994 if (isset($field_desc['null']) && preg_match("/^[^\s]/i", $field_desc['null'])) {
995 if ($field_desc['null'] == 'NOT NULL') {
996 $sql .= " ".$this->sanitize($field_desc['null'], 0, 0, 1);
997 } else {
998 $sql .= " ".$this->sanitize($field_desc['null']);
999 }
1000 }
1001 if (isset($field_desc['default']) && preg_match("/^[^\s]/i", $field_desc['default'])) {
1002 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1003 $sql .= " DEFAULT ".((float) $field_desc['default']);
1004 } elseif ($field_desc['default'] == 'null' || $field_desc['default'] == 'CURRENT_TIMESTAMP') {
1005 $sql .= " DEFAULT ".$this->sanitize($field_desc['default']);
1006 } else {
1007 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'";
1008 }
1009 }
1010 if (isset($field_desc['extra']) && preg_match("/^[^\s]/i", $field_desc['extra'])) {
1011 $sql .= " ".$this->sanitize($field_desc['extra'], 0, 0, 1);
1012 }
1013 $sql .= " ".$this->sanitize($field_position, 0, 0, 1);
1014
1015 dol_syslog(get_class($this)."::DDLAddField ".$sql, LOG_DEBUG);
1016 if ($this->query($sql)) {
1017 return 1;
1018 }
1019 return -1;
1020 }
1021
1022 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1031 public function DDLUpdateField($table, $field_name, $field_desc)
1032 {
1033 // phpcs:enable
1034 $sql = "ALTER TABLE ".$this->sanitize($table);
1035 $sql .= " MODIFY COLUMN ".$this->sanitize($field_name)." ";
1036
1037 if ($field_desc['type'] !== 'datetimegmt') {
1038 $sql .= $this->sanitize($field_desc['type']);
1039 } else {
1040 $sql .= 'datetime';
1041 }
1042
1043 if (in_array($field_desc['type'], array('double', 'int', 'varchar')) && array_key_exists('value', $field_desc) && !empty($field_desc['value'])) {
1044 $sql .= "(".$this->sanitize($field_desc['value']).")";
1045 }
1046 if (isset($field_desc['null']) && ($field_desc['null'] == 'not null' || $field_desc['null'] == 'NOT NULL')) {
1047 // 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
1048 if ($field_desc['type'] == 'varchar' || $field_desc['type'] == 'text') {
1049 $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";
1050 $this->query($sqlbis);
1051 } elseif (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1052 $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";
1053 $this->query($sqlbis);
1054 }
1055
1056 $sql .= " NOT NULL";
1057 }
1058
1059 if (isset($field_desc['default']) && $field_desc['default'] != '') {
1060 if (in_array($field_desc['type'], array('tinyint', 'smallint', 'int', 'double'))) {
1061 $sql .= " DEFAULT ".((float) $field_desc['default']);
1062 } elseif ($field_desc['type'] != 'text') {
1063 $sql .= " DEFAULT '".$this->escape($field_desc['default'])."'"; // Default not supported on text fields
1064 }
1065 }
1066
1067 //print $sql;exit;
1068 dol_syslog(get_class($this)."::DDLUpdateField ".$sql, LOG_DEBUG);
1069 if (!$this->query($sql)) {
1070 return -1;
1071 } else {
1072 return 1;
1073 }
1074 }
1075
1076 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1084 public function DDLDropField($table, $field_name)
1085 {
1086 // phpcs:enable
1087 $tmp_field_name = preg_replace('/[^a-z0-9\.\-\_]/i', '', $field_name);
1088
1089 $sql = "ALTER TABLE ".$this->sanitize($table)." DROP COLUMN `".$this->sanitize($tmp_field_name)."`";
1090 if ($this->query($sql)) {
1091 return 1;
1092 }
1093 $this->error = $this->lasterror();
1094 return -1;
1095 }
1096
1097
1098 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1108 public function DDLCreateUser($dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_pass, $dolibarr_main_db_name)
1109 {
1110 // phpcs:enable
1111 $sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
1112 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1113 $resql = $this->query($sql);
1114 if (!$resql) {
1115 if ($this->lasterrno != 'DB_ERROR_USER_ALREADY_EXISTS') {
1116 return -1;
1117 } else {
1118 // If user already exists, we continue to set permissions
1119 dol_syslog(get_class($this)."::DDLCreateUser sql=".$sql, LOG_WARNING);
1120 }
1121 }
1122
1123 // Redo with localhost forced (sometimes user is created on %)
1124 $sql = "CREATE USER '".$this->escape($dolibarr_main_db_user)."'@'localhost' IDENTIFIED BY '".$this->escape($dolibarr_main_db_pass)."'";
1125 $resql = $this->query($sql);
1126
1127 $sql = "GRANT ALL PRIVILEGES ON `".$this->sanitize($dolibarr_main_db_name)."`.* TO '".$this->escape($dolibarr_main_db_user)."'@'".$this->escape($dolibarr_main_db_host)."'";
1128 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log
1129 $resql = $this->query($sql);
1130 if (!$resql) {
1131 $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)."'";
1132 return -1;
1133 }
1134
1135 $sql = "FLUSH Privileges";
1136
1137 dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG);
1138 $resql = $this->query($sql);
1139 if (!$resql) {
1140 return -1;
1141 }
1142
1143 return 1;
1144 }
1145
1153 public function getDefaultCharacterSetDatabase()
1154 {
1155 $resql = $this->query("SHOW VARIABLES LIKE 'character_set_database'");
1156 if (!$resql) {
1157 // version Mysql < 4.1.1
1158 return $this->forcecharset;
1159 }
1160 $liste = $this->fetch_array($resql);
1161 $tmpval = $liste['Value'];
1162
1163 return $tmpval;
1164 }
1165
1171 public function getListOfCharacterSet()
1172 {
1173 $resql = $this->query('SHOW CHARSET');
1174 $liste = array();
1175 if ($resql) {
1176 $i = 0;
1177 while ($obj = $this->fetch_object($resql)) {
1178 $liste[$i]['charset'] = $obj->Charset;
1179 $liste[$i]['description'] = $obj->Description;
1180 $i++;
1181 }
1182 $this->free($resql);
1183 } else {
1184 // version Mysql < 4.1.1
1185 return null;
1186 }
1187 return $liste;
1188 }
1189
1196 public function getDefaultCollationDatabase()
1197 {
1198 $resql = $this->query("SHOW VARIABLES LIKE 'collation_database'");
1199 if (!$resql) {
1200 // version Mysql < 4.1.1
1201 return $this->forcecollate;
1202 }
1203 $liste = $this->fetch_array($resql);
1204 $tmpval = $liste['Value'];
1205
1206 return $tmpval;
1207 }
1208
1214 public function getListOfCollation()
1215 {
1216 $resql = $this->query('SHOW COLLATION');
1217 $liste = array();
1218 if ($resql) {
1219 $i = 0;
1220 while ($obj = $this->fetch_object($resql)) {
1221 $liste[$i]['collation'] = $obj->Collation;
1222 $i++;
1223 }
1224 $this->free($resql);
1225 } else {
1226 // version Mysql < 4.1.1
1227 return null;
1228 }
1229 return $liste;
1230 }
1231
1237 public function getPathOfDump()
1238 {
1239 $fullpathofdump = '/pathtomysqldump/mysqldump';
1240
1241 $resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
1242 if ($resql) {
1243 $liste = $this->fetch_array($resql);
1244 $basedir = $liste['Value'];
1245 $fullpathofdump = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysqldump';
1246 }
1247 return $fullpathofdump;
1248 }
1249
1255 public function getPathOfRestore()
1256 {
1257 $fullpathofimport = '/pathtomysql/mysql';
1258
1259 $resql = $this->query("SHOW VARIABLES LIKE 'basedir'");
1260 if ($resql) {
1261 $liste = $this->fetch_array($resql);
1262 $basedir = $liste['Value'];
1263 $fullpathofimport = $basedir.(preg_match('/\/$/', $basedir) ? '' : '/').'bin/mysql';
1264 }
1265 return $fullpathofimport;
1266 }
1267
1274 public function getServerParametersValues($filter = '')
1275 {
1276 $result = array();
1277
1278 $sql = 'SHOW VARIABLES';
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
1298 public function getServerStatusValues($filter = '')
1299 {
1300 $result = array();
1301
1302 $sql = 'SHOW STATUS';
1303 if ($filter) {
1304 $sql .= " LIKE '".$this->escape($filter)."'";
1305 }
1306 $resql = $this->query($sql);
1307 if ($resql) {
1308 while ($obj = $this->fetch_object($resql)) {
1309 $result[$obj->Variable_name] = $obj->Value;
1310 }
1311 }
1312
1313 return $result;
1314 }
1315
1322 public function getNextAutoIncrementId($table)
1323 {
1324 // Request to get last status of table
1325 $sql = "SHOW TABLE STATUS LIKE '".$this->escape($table)."'";
1326 $result = $this->query($sql);
1327
1328 if ($result) {
1329 $obj = $this->fetch_object($result);
1330 if ($obj && isset($obj->Auto_increment)) {
1331 return (int) $obj->Auto_increment;
1332 }
1333 }
1334
1335 return -1;
1336 }
1337
1344 public function prepare($sql)
1345 {
1346 if (!$this->connected) {
1347 $this->lasterror = 'Not connected to database';
1348 return false;
1349 }
1350 $stmt = $this->db->prepare($sql);
1351 if ($stmt === false) {
1352 $this->lasterror = $this->db->error;
1353 $this->lastqueryerror = $sql;
1354 return false;
1355 }
1356
1357 return $stmt;
1358 }
1359}
1360
1361if (class_exists('mysqli')) {
1365 class mysqliDoli extends mysqli
1366 {
1378 public function __construct($host, $user, $pass, $name, $port = 0, $socket = "") // @phpstan-ignore constructor.unusedParameter
1379 {
1380 $flags = 0;
1381 if (PHP_VERSION_ID >= 80100) {
1382 parent::__construct();
1383 } else {
1384 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
1385 parent::init();
1386 }
1387 if (strpos($host, 'ssl://') === 0) {
1388 $host = substr($host, 6);
1389 parent::options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, 0);
1390 // Suppress false positive @phan-suppress-next-line PhanTypeMismatchArgumentInternalProbablyReal
1391 parent::ssl_set(null, null, "", null, null);
1392 $flags = MYSQLI_CLIENT_SSL;
1393 }
1394 parent::real_connect($host, $user, $pass, $name, $port, $socket, $flags);
1395 }
1396 }
1397}
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_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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
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