1<?php 2/** 3 * Copyright since 2007 PrestaShop SA and Contributors 4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 5 * 6 * NOTICE OF LICENSE 7 * 8 * This source file is subject to the Open Software License (OSL 3.0) 9 * that is bundled with this package in the file LICENSE.md. 10 * It is also available through the world-wide-web at this URL: 11 * https://opensource.org/licenses/OSL-3.0 12 * If you did not receive a copy of the license and are unable to 13 * obtain it through the world-wide-web, please send an email 14 * to license@prestashop.com so we can send you a copy immediately. 15 * 16 * DISCLAIMER 17 * 18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 19 * versions in the future. If you wish to customize PrestaShop for your 20 * needs please refer to https://devdocs.prestashop.com/ for more information. 21 * 22 * @author PrestaShop SA and Contributors <contact@prestashop.com> 23 * @copyright Since 2007 PrestaShop SA and Contributors 24 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 25 */ 26 27/** 28 * Class DbMySQLiCore. 29 * 30 * @since 1.5.0,1 31 */ 32class DbMySQLiCore extends Db 33{ 34 /** @var mysqli */ 35 protected $link; 36 37 /** @var mysqli_result */ 38 protected $result; 39 40 /** 41 * Tries to connect to the database. 42 * 43 * @see DbCore::connect() 44 * 45 * @return mysqli 46 * 47 * @throws PrestaShopDatabaseException 48 */ 49 public function connect() 50 { 51 $socket = false; 52 $port = false; 53 if (Tools::strpos($this->server, ':') !== false) { 54 list($server, $port) = explode(':', $this->server); 55 if (is_numeric($port) === false) { 56 $socket = $port; 57 $port = false; 58 } 59 } elseif (Tools::strpos($this->server, '/') !== false) { 60 $socket = $this->server; 61 } 62 63 if ($socket) { 64 $this->link = @new mysqli(null, $this->user, $this->password, $this->database, null, $socket); 65 } elseif ($port) { 66 $this->link = @new mysqli($server, $this->user, $this->password, $this->database, $port); 67 } else { 68 $this->link = @new mysqli($this->server, $this->user, $this->password, $this->database); 69 } 70 71 // Do not use object way for error because this work bad before PHP 5.2.9 72 if (mysqli_connect_error()) { 73 throw new PrestaShopDatabaseException(sprintf(Tools::displayError('Link to database cannot be established: %s'), mysqli_connect_error())); 74 } 75 76 // UTF-8 support 77 if (!$this->link->query('SET NAMES utf8mb4')) { 78 throw new PrestaShopDatabaseException(Tools::displayError('PrestaShop Fatal error: no utf-8 support. Please check your server configuration.')); 79 } 80 81 $this->link->query('SET SESSION sql_mode = \'\''); 82 83 return $this->link; 84 } 85 86 /** 87 * Tries to connect and create a new database. 88 * 89 * @param string $host 90 * @param string|null $user 91 * @param string|null $password 92 * @param string|null $database 93 * @param bool $dropit if true, drops the created database 94 * 95 * @return bool|mysqli_result 96 */ 97 public static function createDatabase($host, $user = null, $password = null, $database = null, $dropit = false) 98 { 99 if (strpos($host, ':') !== false) { 100 list($host, $port) = explode(':', $host); 101 $link = @new mysqli($host, $user, $password, null, $port); 102 } else { 103 $link = @new mysqli($host, $user, $password); 104 } 105 $success = $link->query('CREATE DATABASE `' . str_replace('`', '\\`', $database) . '`'); 106 if ($dropit && ($link->query('DROP DATABASE `' . str_replace('`', '\\`', $database) . '`') !== false)) { 107 return true; 108 } 109 110 return $success; 111 } 112 113 /** 114 * Destroys the database connection link. 115 * 116 * @see DbCore::disconnect() 117 */ 118 public function disconnect() 119 { 120 @$this->link->close(); 121 } 122 123 /** 124 * Executes an SQL statement, returning a result set as a mysqli_result object or true/false. 125 * 126 * @see DbCore::_query() 127 * 128 * @param string $sql 129 * 130 * @return bool|mysqli_result 131 */ 132 protected function _query($sql) 133 { 134 return $this->link->query($sql); 135 } 136 137 /** 138 * Returns the next row from the result set. 139 * 140 * @see DbCore::nextRow() 141 * 142 * @param bool|mysqli_result $result 143 * 144 * @return array|bool 145 */ 146 public function nextRow($result = false) 147 { 148 if (!$result) { 149 $result = $this->result; 150 } 151 152 if (!is_object($result)) { 153 return false; 154 } 155 156 return $result->fetch_assoc(); 157 } 158 159 /** 160 * Returns all rows from the result set. 161 * 162 * @see DbCore::getAll() 163 * 164 * @param bool|mysqli_result $result 165 * 166 * @return array|false 167 */ 168 protected function getAll($result = false) 169 { 170 if (!$result) { 171 $result = $this->result; 172 } 173 174 if (!is_object($result)) { 175 return false; 176 } 177 178 if (method_exists($result, 'fetch_all')) { 179 return $result->fetch_all(MYSQLI_ASSOC); 180 } else { 181 $ret = []; 182 183 while ($row = $this->nextRow($result)) { 184 $ret[] = $row; 185 } 186 187 return $ret; 188 } 189 } 190 191 /** 192 * Returns row count from the result set. 193 * 194 * @see DbCore::_numRows() 195 * 196 * @param bool|mysqli_result $result 197 * 198 * @return int 199 */ 200 protected function _numRows($result) 201 { 202 return $result->num_rows; 203 } 204 205 /** 206 * Returns ID of the last inserted row. 207 * 208 * @see DbCore::Insert_ID() 209 * 210 * @return string|int 211 */ 212 public function Insert_ID() 213 { 214 return $this->link->insert_id; 215 } 216 217 /** 218 * Return the number of rows affected by the last SQL query. 219 * 220 * @see DbCore::Affected_Rows() 221 * 222 * @return int 223 */ 224 public function Affected_Rows() 225 { 226 return $this->link->affected_rows; 227 } 228 229 /** 230 * Returns error message. 231 * 232 * @see DbCore::getMsgError() 233 * 234 * @param bool $query 235 * 236 * @return string 237 */ 238 public function getMsgError($query = false) 239 { 240 return $this->link->error; 241 } 242 243 /** 244 * Returns error code. 245 * 246 * @see DbCore::getNumberError() 247 * 248 * @return int 249 */ 250 public function getNumberError() 251 { 252 return $this->link->errno; 253 } 254 255 /** 256 * Returns database server version. 257 * 258 * @see DbCore::getVersion() 259 * 260 * @return string 261 */ 262 public function getVersion() 263 { 264 return $this->getValue('SELECT VERSION()'); 265 } 266 267 /** 268 * Escapes illegal characters in a string. 269 * 270 * @see DbCore::_escape() 271 * 272 * @param string $str 273 * 274 * @return string 275 */ 276 public function _escape($str) 277 { 278 return $this->link->real_escape_string($str); 279 } 280 281 /** 282 * Switches to a different database. 283 * 284 * @see DbCore::set_db() 285 * 286 * @param string $db_name 287 * 288 * @return bool 289 */ 290 public function set_db($db_name) 291 { 292 return $this->link->query('USE `' . bqSQL($db_name) . '`'); 293 } 294 295 /** 296 * Try a connection to the database and check if at least one table with same prefix exists. 297 * 298 * @see Db::hasTableWithSamePrefix() 299 * 300 * @param string $server Server address 301 * @param string $user Login for database connection 302 * @param string $pwd Password for database connection 303 * @param string $db Database name 304 * @param string $prefix Tables prefix 305 * 306 * @return bool 307 */ 308 public static function hasTableWithSamePrefix($server, $user, $pwd, $db, $prefix) 309 { 310 $link = @new mysqli($server, $user, $pwd, $db); 311 if (mysqli_connect_error()) { 312 return false; 313 } 314 315 $sql = 'SHOW TABLES LIKE \'' . $prefix . '%\''; 316 $result = $link->query($sql); 317 318 return (bool) $result->fetch_assoc(); 319 } 320 321 /** 322 * Try a connection to the database. 323 * 324 * @see Db::checkConnection() 325 * 326 * @param string $server Server address 327 * @param string $user Login for database connection 328 * @param string $pwd Password for database connection 329 * @param string $db Database name 330 * @param bool $newDbLink 331 * @param string|bool $engine 332 * @param int $timeout 333 * 334 * @return int Error code or 0 if connection was successful 335 */ 336 public static function tryToConnect($server, $user, $pwd, $db, $new_db_link = true, $engine = null, $timeout = 5) 337 { 338 $link = mysqli_init(); 339 if (!$link) { 340 return -1; 341 } 342 343 if (!$link->options(MYSQLI_OPT_CONNECT_TIMEOUT, $timeout)) { 344 return 1; 345 } 346 347 // There is an @ because mysqli throw a warning when the database does not exists 348 if (!@$link->real_connect($server, $user, $pwd, $db)) { 349 return (mysqli_connect_errno() == 1049) ? 2 : 1; 350 } 351 352 $link->close(); 353 354 return 0; 355 } 356 357 /** 358 * Selects best table engine. 359 * 360 * @return string 361 */ 362 public function getBestEngine() 363 { 364 $value = 'InnoDB'; 365 366 $sql = 'SHOW VARIABLES WHERE Variable_name = \'have_innodb\''; 367 $result = $this->link->query($sql); 368 if (!$result) { 369 $value = 'MyISAM'; 370 } 371 $row = $result->fetch_assoc(); 372 if (!$row || strtolower($row['Value']) != 'yes') { 373 $value = 'MyISAM'; 374 } 375 376 /* MySQL >= 5.6 */ 377 $sql = 'SHOW ENGINES'; 378 $result = $this->link->query($sql); 379 while ($row = $result->fetch_assoc()) { 380 if ($row['Engine'] == 'InnoDB') { 381 if (in_array($row['Support'], ['DEFAULT', 'YES'])) { 382 $value = 'InnoDB'; 383 } 384 385 break; 386 } 387 } 388 389 return $value; 390 } 391 392 /** 393 * Tries to connect to the database and create a table (checking creation privileges). 394 * 395 * @param string $server 396 * @param string $user 397 * @param string $pwd 398 * @param string $db 399 * @param string $prefix 400 * @param string|null $engine Table engine 401 * 402 * @return bool|string True, false or error 403 */ 404 public static function checkCreatePrivilege($server, $user, $pwd, $db, $prefix, $engine = null) 405 { 406 $link = @new mysqli($server, $user, $pwd, $db); 407 if (mysqli_connect_error()) { 408 return false; 409 } 410 411 $enginesToTest = ['InnoDB', 'MyISAM']; 412 if ($engine !== null) { 413 $enginesToTest = [$engine]; 414 } 415 416 foreach ($enginesToTest as $engineToTest) { 417 $result = $link->query(' 418 CREATE TABLE `' . $prefix . 'test` ( 419 `test` tinyint(1) unsigned NOT NULL 420 ) ENGINE=' . $engineToTest); 421 422 if ($result) { 423 $link->query('DROP TABLE `' . $prefix . 'test`'); 424 425 return true; 426 } 427 } 428 429 return $link->error; 430 } 431 432 /** 433 * Tries to connect to the database and select content (checking select privileges). 434 * 435 * @param string $server 436 * @param string $user 437 * @param string $pwd 438 * @param string $db 439 * @param string $prefix 440 * @param string|null $engine Table engine 441 * 442 * @return bool|string True, false or error 443 */ 444 public static function checkSelectPrivilege($server, $user, $pwd, $db, $prefix, $engine = null) 445 { 446 $link = @new mysqli($server, $user, $pwd, $db); 447 if (mysqli_connect_error()) { 448 return false; 449 } 450 451 if ($engine === null) { 452 $engine = 'MyISAM'; 453 } 454 455 // Create a table 456 $link->query(' 457 CREATE TABLE `' . $prefix . 'test` ( 458 `test` tinyint(1) unsigned NOT NULL 459 ) ENGINE=' . $engine); 460 461 // Select content 462 $result = $link->query('SELECT * FROM `' . $prefix . 'test`'); 463 464 // Drop the table 465 $link->query('DROP TABLE `' . $prefix . 'test`'); 466 467 if (!$result) { 468 return $link->error; 469 } 470 471 return true; 472 } 473 474 /** 475 * Try a connection to the database and set names to UTF-8. 476 * 477 * @see Db::checkEncoding() 478 * 479 * @param string $server Server address 480 * @param string $user Login for database connection 481 * @param string $pwd Password for database connection 482 * 483 * @return bool 484 */ 485 public static function tryUTF8($server, $user, $pwd) 486 { 487 $link = @new mysqli($server, $user, $pwd); 488 $ret = $link->query('SET NAMES utf8mb4'); 489 $link->close(); 490 491 return $ret; 492 } 493} 494