[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework Database Package 4 * 5 * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\Database\Mysql; 10 11 use Joomla\Database\Exception\ConnectionFailureException; 12 use Joomla\Database\Pdo\PdoDriver; 13 use Joomla\Database\UTF8MB4SupportInterface; 14 15 /** 16 * MySQL database driver supporting PDO based connections 17 * 18 * @link https://www.php.net/manual/en/ref.pdo-mysql.php 19 * @since 1.0 20 */ 21 class MysqlDriver extends PdoDriver implements UTF8MB4SupportInterface 22 { 23 /** 24 * The name of the database driver. 25 * 26 * @var string 27 * @since 1.0 28 */ 29 public $name = 'mysql'; 30 31 /** 32 * The character(s) used to quote SQL statement names such as table names or field names, etc. 33 * 34 * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the 35 * opening quote and the second for the closing quote. 36 * 37 * @var string 38 * @since 1.0 39 */ 40 protected $nameQuote = '`'; 41 42 /** 43 * The null or zero representation of a timestamp for the database driver. 44 * 45 * @var string 46 * @since 1.0 47 */ 48 protected $nullDate = '0000-00-00 00:00:00'; 49 50 /** 51 * True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding. 52 * 53 * @var boolean 54 * @since 1.4.0 55 */ 56 protected $utf8mb4 = false; 57 58 /** 59 * True if the database engine is MariaDB. 60 * 61 * @var boolean 62 * @since 2.0.0 63 */ 64 protected $mariadb = false; 65 66 /** 67 * The minimum supported database version. 68 * 69 * @var string 70 * @since 1.0 71 */ 72 protected static $dbMinimum = '5.6'; 73 74 /** 75 * The minimum supported MariaDB database version. 76 * 77 * @var string 78 * @since 2.0.0 79 */ 80 protected static $dbMinMariadb = '10.0'; 81 82 /** 83 * The default cipher suite for TLS connections. 84 * 85 * @var array 86 * @since 2.0.0 87 */ 88 protected static $defaultCipherSuite = [ 89 'AES128-GCM-SHA256', 90 'AES256-GCM-SHA384', 91 'AES128-CBC-SHA256', 92 'AES256-CBC-SHA384', 93 'DES-CBC3-SHA', 94 ]; 95 96 /** 97 * Constructor. 98 * 99 * @param array $options Array of database options with keys: host, user, password, database, select. 100 * 101 * @since 1.0 102 */ 103 public function __construct(array $options) 104 { 105 /** 106 * sql_mode to MySql 5.7.8+ default strict mode minus ONLY_FULL_GROUP_BY 107 * 108 * @link https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-8.html#mysqld-5-7-8-sql-mode 109 */ 110 $sqlModes = [ 111 'STRICT_TRANS_TABLES', 112 'ERROR_FOR_DIVISION_BY_ZERO', 113 'NO_ENGINE_SUBSTITUTION', 114 ]; 115 116 // Get some basic values from the options. 117 $options['driver'] = 'mysql'; 118 $options['charset'] = $options['charset'] ?? 'utf8'; 119 $options['sqlModes'] = isset($options['sqlModes']) ? (array) $options['sqlModes'] : $sqlModes; 120 121 $this->charset = $options['charset']; 122 123 /* 124 * Pre-populate the UTF-8 Multibyte compatibility flag. Unfortunately PDO won't report the server version unless we're connected to it, 125 * and we cannot connect to it unless we know if it supports utf8mb4, which requires us knowing the server version. Because of this 126 * chicken and egg issue, we _assume_ it's supported and we'll just catch any problems at connection time. 127 */ 128 $this->utf8mb4 = $options['charset'] === 'utf8mb4'; 129 130 // Finalize initialisation. 131 parent::__construct($options); 132 } 133 134 /** 135 * Connects to the database if needed. 136 * 137 * @return void Returns void if the database connected successfully. 138 * 139 * @since 1.0 140 * @throws \RuntimeException 141 */ 142 public function connect() 143 { 144 if ($this->getConnection()) 145 { 146 return; 147 } 148 149 // For SSL/TLS connection encryption. 150 if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true) 151 { 152 $sslContextIsNull = true; 153 154 // If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options. 155 foreach (['cipher', 'ca', 'capath', 'key', 'cert'] as $key => $value) 156 { 157 if ($this->options['ssl'][$value] !== null) 158 { 159 $this->options['driverOptions'][constant('\PDO::MYSQL_ATTR_SSL_' . strtoupper($value))] = $this->options['ssl'][$value]; 160 $sslContextIsNull = false; 161 } 162 } 163 164 // PDO, if no cipher, ca, capath, cert and key are set, can't start TLS one-way connection, set a common ciphers suite to force it. 165 if ($sslContextIsNull === true) 166 { 167 $this->options['driverOptions'][\PDO::MYSQL_ATTR_SSL_CIPHER] = implode(':', static::$defaultCipherSuite); 168 } 169 170 // If customised, for capable systems (PHP 7.0.14+ and 7.1.4+) verify certificate chain and Common Name to driver options. 171 if ($this->options['ssl']['verify_server_cert'] !== null && defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) 172 { 173 $this->options['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->options['ssl']['verify_server_cert']; 174 } 175 } 176 177 try 178 { 179 // Try to connect to MySQL 180 parent::connect(); 181 } 182 catch (ConnectionFailureException $e) 183 { 184 // If the connection failed, but not because of the wrong character set, then bubble up the exception. 185 if (!$this->utf8mb4) 186 { 187 throw $e; 188 } 189 190 /* 191 * Otherwise, try connecting again without using utf8mb4 and see if maybe that was the problem. If the connection succeeds, then we 192 * will have learned that the client end of the connection does not support utf8mb4. 193 */ 194 $this->utf8mb4 = false; 195 $this->options['charset'] = 'utf8'; 196 197 parent::connect(); 198 } 199 200 $serverVersion = $this->getVersion(); 201 202 $this->mariadb = stripos($serverVersion, 'mariadb') !== false; 203 204 if ($this->utf8mb4) 205 { 206 // At this point we know the client supports utf8mb4. Now we must check if the server supports utf8mb4 as well. 207 $this->utf8mb4 = version_compare($serverVersion, '5.5.3', '>='); 208 209 if ($this->mariadb && version_compare($serverVersion, '10.0.0', '<')) 210 { 211 $this->utf8mb4 = false; 212 } 213 214 if (!$this->utf8mb4) 215 { 216 // Reconnect with the utf8 character set. 217 parent::disconnect(); 218 $this->options['charset'] = 'utf8'; 219 parent::connect(); 220 } 221 } 222 223 // If needed, set the sql modes. 224 if ($this->options['sqlModes'] !== []) 225 { 226 $this->connection->query('SET @@SESSION.sql_mode = \'' . implode(',', $this->options['sqlModes']) . '\';'); 227 } 228 229 $this->setOption(\PDO::ATTR_EMULATE_PREPARES, true); 230 } 231 232 /** 233 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. 234 * 235 * Used when the server doesn't support UTF-8 Multibyte. 236 * 237 * @param string $query The query to convert 238 * 239 * @return string The converted query 240 * 241 * @since 1.4.0 242 */ 243 public function convertUtf8mb4QueryToUtf8($query) 244 { 245 if ($this->hasUTF8mb4Support()) 246 { 247 return $query; 248 } 249 250 // If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert 251 $beginningOfQuery = substr($query, 0, 12); 252 $beginningOfQuery = strtoupper($beginningOfQuery); 253 254 if (!\in_array($beginningOfQuery, ['ALTER TABLE ', 'CREATE TABLE'], true)) 255 { 256 return $query; 257 } 258 259 // Replace utf8mb4 with utf8 260 return str_replace('utf8mb4', 'utf8', $query); 261 } 262 263 /** 264 * Test to see if the MySQL connector is available. 265 * 266 * @return boolean True on success, false otherwise. 267 * 268 * @since 1.0 269 */ 270 public static function isSupported() 271 { 272 return class_exists('\\PDO') && \in_array('mysql', \PDO::getAvailableDrivers(), true); 273 } 274 275 /** 276 * Select a database for use. 277 * 278 * @param string $database The name of the database to select for use. 279 * 280 * @return boolean 281 * 282 * @since 1.0 283 * @throws \RuntimeException 284 */ 285 public function select($database) 286 { 287 $this->connect(); 288 289 $this->setQuery('USE ' . $this->quoteName($database)) 290 ->execute(); 291 292 return true; 293 } 294 295 /** 296 * Return the query string to alter the database character set. 297 * 298 * @param string $dbName The database name 299 * 300 * @return string The query that alter the database query string 301 * 302 * @since 2.0.0 303 */ 304 public function getAlterDbCharacterSet($dbName) 305 { 306 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; 307 308 return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`'; 309 } 310 311 /** 312 * Method to get the database collation in use by sampling a text field of a table in the database. 313 * 314 * @return string|boolean The collation in use by the database (string) or boolean false if not supported. 315 * 316 * @since 1.0 317 * @throws \RuntimeException 318 */ 319 public function getCollation() 320 { 321 $this->connect(); 322 323 return $this->setQuery('SELECT @@collation_database;')->loadResult(); 324 } 325 326 /** 327 * Method to get the database connection collation in use by sampling a text field of a table in the database. 328 * 329 * @return string|boolean The collation in use by the database connection (string) or boolean false if not supported. 330 * 331 * @since 1.6.0 332 * @throws \RuntimeException 333 */ 334 public function getConnectionCollation() 335 { 336 $this->connect(); 337 338 return $this->setQuery('SELECT @@collation_connection;')->loadResult(); 339 } 340 341 /** 342 * Method to get the database encryption details (cipher and protocol) in use. 343 * 344 * @return string The database encryption details. 345 * 346 * @since 2.0.0 347 * @throws \RuntimeException 348 */ 349 public function getConnectionEncryption(): string 350 { 351 $this->connect(); 352 353 $variables = $this->setQuery('SHOW SESSION STATUS WHERE `Variable_name` IN (\'Ssl_version\', \'Ssl_cipher\')') 354 ->loadObjectList('Variable_name'); 355 356 if (!empty($variables['Ssl_cipher']->Value)) 357 { 358 return $variables['Ssl_version']->Value . ' (' . $variables['Ssl_cipher']->Value . ')'; 359 } 360 361 return ''; 362 } 363 364 /** 365 * Method to test if the database TLS connections encryption are supported. 366 * 367 * @return boolean Whether the database supports TLS connections encryption. 368 * 369 * @since 2.0.0 370 */ 371 public function isConnectionEncryptionSupported(): bool 372 { 373 $this->connect(); 374 375 $variables = $this->setQuery('SHOW SESSION VARIABLES WHERE `Variable_name` IN (\'have_ssl\')')->loadObjectList('Variable_name'); 376 377 return !empty($variables['have_ssl']->Value) && $variables['have_ssl']->Value === 'YES'; 378 } 379 380 /** 381 * Return the query string to create new Database. 382 * 383 * @param stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. 384 * @param boolean $utf True if the database supports the UTF-8 character set. 385 * 386 * @return string The query that creates database 387 * 388 * @since 2.0.0 389 */ 390 protected function getCreateDatabaseQuery($options, $utf) 391 { 392 if ($utf) 393 { 394 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; 395 $collation = $charset . '_unicode_ci'; 396 397 return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`'; 398 } 399 400 return 'CREATE DATABASE ' . $this->quoteName($options->db_name); 401 } 402 403 /** 404 * Shows the table CREATE statement that creates the given tables. 405 * 406 * @param array|string $tables A table name or a list of table names. 407 * 408 * @return array A list of the create SQL for the tables. 409 * 410 * @since 1.0 411 * @throws \RuntimeException 412 */ 413 public function getTableCreate($tables) 414 { 415 $this->connect(); 416 417 // Initialise variables. 418 $result = []; 419 420 // Sanitize input to an array and iterate over the list. 421 $tables = (array) $tables; 422 423 foreach ($tables as $table) 424 { 425 $row = $this->setQuery('SHOW CREATE TABLE ' . $this->quoteName($table))->loadRow(); 426 427 // Populate the result array based on the create statements. 428 $result[$table] = $row[1]; 429 } 430 431 return $result; 432 } 433 434 /** 435 * Retrieves field information about a given table. 436 * 437 * @param string $table The name of the database table. 438 * @param boolean $typeOnly True to only return field types. 439 * 440 * @return array An array of fields for the database table. 441 * 442 * @since 1.0 443 * @throws \RuntimeException 444 */ 445 public function getTableColumns($table, $typeOnly = true) 446 { 447 $this->connect(); 448 449 $result = []; 450 451 // Set the query to get the table fields statement. 452 $fields = $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($table))->loadObjectList(); 453 454 // If we only want the type as the value add just that to the list. 455 if ($typeOnly) 456 { 457 foreach ($fields as $field) 458 { 459 $result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type); 460 } 461 } 462 // If we want the whole field data object add that to the list. 463 else 464 { 465 foreach ($fields as $field) 466 { 467 $result[$field->Field] = $field; 468 } 469 } 470 471 return $result; 472 } 473 474 /** 475 * Get the details list of keys for a table. 476 * 477 * @param string $table The name of the table. 478 * 479 * @return array An array of the column specification for the table. 480 * 481 * @since 1.0 482 * @throws \RuntimeException 483 */ 484 public function getTableKeys($table) 485 { 486 $this->connect(); 487 488 // Get the details columns information. 489 return $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table))->loadObjectList(); 490 } 491 492 /** 493 * Method to get an array of all tables in the database. 494 * 495 * @return array An array of all the tables in the database. 496 * 497 * @since 1.0 498 * @throws \RuntimeException 499 */ 500 public function getTableList() 501 { 502 $this->connect(); 503 504 // Set the query to get the tables statement. 505 return $this->setQuery('SHOW TABLES')->loadColumn(); 506 } 507 508 /** 509 * Get the version of the database connector. 510 * 511 * @return string The database connector version. 512 * 513 * @since 2.0.0 514 */ 515 public function getVersion() 516 { 517 $this->connect(); 518 519 $version = $this->getOption(\PDO::ATTR_SERVER_VERSION); 520 521 if (stripos($version, 'mariadb') !== false) 522 { 523 // MariaDB: Strip off any leading '5.5.5-', if present 524 return preg_replace('/^5\.5\.5-/', '', $version); 525 } 526 527 return $version; 528 } 529 530 /** 531 * Get the minimum supported database version. 532 * 533 * @return string 534 * 535 * @since 2.0.0 536 */ 537 public function getMinimum() 538 { 539 return $this->mariadb ? static::$dbMinMariadb : static::$dbMinimum; 540 } 541 542 /** 543 * Get the null or zero representation of a timestamp for the database driver. 544 * 545 * @return string 546 * 547 * @since 2.0.0 548 */ 549 public function getNullDate() 550 { 551 // Check the session sql mode; 552 if (\in_array('NO_ZERO_DATE', $this->options['sqlModes']) !== false) 553 { 554 $this->nullDate = '1000-01-01 00:00:00'; 555 } 556 557 return $this->nullDate; 558 } 559 560 /** 561 * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. 562 * 563 * @return boolean True if the database engine supports UTF-8 Multibyte. 564 * 565 * @since 2.0.0 566 */ 567 public function hasUTF8mb4Support() 568 { 569 return $this->utf8mb4; 570 } 571 572 /** 573 * Determine if the database engine is MariaDB. 574 * 575 * @return boolean 576 * 577 * @since 2.0.0 578 */ 579 public function isMariaDb(): bool 580 { 581 $this->connect(); 582 583 return $this->mariadb; 584 } 585 586 /** 587 * Locks a table in the database. 588 * 589 * @param string $table The name of the table to unlock. 590 * 591 * @return $this 592 * 593 * @since 1.0 594 * @throws \RuntimeException 595 */ 596 public function lockTable($table) 597 { 598 $this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE') 599 ->execute(); 600 601 return $this; 602 } 603 604 /** 605 * Renames a table in the database. 606 * 607 * @param string $oldTable The name of the table to be renamed 608 * @param string $newTable The new name for the table. 609 * @param string $backup Not used by MySQL. 610 * @param string $prefix Not used by MySQL. 611 * 612 * @return $this 613 * 614 * @since 1.0 615 * @throws \RuntimeException 616 */ 617 public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) 618 { 619 $this->setQuery('RENAME TABLE ' . $this->quoteName($oldTable) . ' TO ' . $this->quoteName($newTable)) 620 ->execute(); 621 622 return $this; 623 } 624 625 /** 626 * Inserts a row into a table based on an object's properties. 627 * 628 * @param string $table The name of the database table to insert into. 629 * @param object $object A reference to an object whose public properties match the table fields. 630 * @param string $key The name of the primary key. If provided the object property is updated. 631 * 632 * @return boolean 633 * 634 * @since 2.0.0 635 * @throws \RuntimeException 636 */ 637 public function insertObject($table, &$object, $key = null) 638 { 639 $fields = []; 640 $values = []; 641 $tableColumns = $this->getTableColumns($table); 642 643 // Iterate over the object variables to build the query fields and values. 644 foreach (get_object_vars($object) as $k => $v) 645 { 646 // Skip columns that don't exist in the table. 647 if (!array_key_exists($k, $tableColumns)) 648 { 649 continue; 650 } 651 652 // Only process non-null scalars. 653 if (\is_array($v) || \is_object($v) || $v === null) 654 { 655 continue; 656 } 657 658 // Ignore any internal fields. 659 if ($k[0] === '_') 660 { 661 continue; 662 } 663 664 // Ignore null datetime fields. 665 if ($tableColumns[$k] === 'datetime' && empty($v)) 666 { 667 continue; 668 } 669 670 // Ignore null integer fields. 671 if (stristr($tableColumns[$k], 'int') !== false && $v === '') 672 { 673 continue; 674 } 675 676 // Prepare and sanitize the fields and values for the database query. 677 $fields[] = $this->quoteName($k); 678 $values[] = $this->quote($v); 679 } 680 681 // Create the base insert statement. 682 $query = $this->getQuery(true) 683 ->insert($this->quoteName($table)) 684 ->columns($fields) 685 ->values(implode(',', $values)); 686 687 // Set the query and execute the insert. 688 $this->setQuery($query)->execute(); 689 690 // Update the primary key if it exists. 691 $id = $this->insertid(); 692 693 if ($key && $id && \is_string($key)) 694 { 695 $object->$key = $id; 696 } 697 698 return true; 699 } 700 701 /** 702 * Method to escape a string for usage in an SQL statement. 703 * 704 * Oracle escaping reference: 705 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F 706 * 707 * SQLite escaping notes: 708 * http://www.sqlite.org/faq.html#q14 709 * 710 * Method body is as implemented by the Zend Framework 711 * 712 * Note: Using query objects with bound variables is preferable to the below. 713 * 714 * @param string $text The string to be escaped. 715 * @param boolean $extra Unused optional parameter to provide extra escaping. 716 * 717 * @return string The escaped string. 718 * 719 * @since 1.0 720 */ 721 public function escape($text, $extra = false) 722 { 723 if (\is_int($text)) 724 { 725 return $text; 726 } 727 728 if (\is_float($text)) 729 { 730 // Force the dot as a decimal point. 731 return str_replace(',', '.', (string) $text); 732 } 733 734 $this->connect(); 735 736 $result = substr($this->connection->quote($text), 1, -1); 737 738 if ($extra) 739 { 740 $result = addcslashes($result, '%_'); 741 } 742 743 return $result; 744 } 745 746 /** 747 * Unlocks tables in the database. 748 * 749 * @return $this 750 * 751 * @since 1.0 752 * @throws \RuntimeException 753 */ 754 public function unlockTables() 755 { 756 $this->setQuery('UNLOCK TABLES') 757 ->execute(); 758 759 return $this; 760 } 761 762 /** 763 * Method to commit a transaction. 764 * 765 * @param boolean $toSavepoint If true, commit to the last savepoint. 766 * 767 * @return void 768 * 769 * @since 1.0 770 * @throws \RuntimeException 771 */ 772 public function transactionCommit($toSavepoint = false) 773 { 774 $this->connect(); 775 776 if (!$toSavepoint || $this->transactionDepth <= 1) 777 { 778 parent::transactionCommit($toSavepoint); 779 } 780 else 781 { 782 $this->transactionDepth--; 783 } 784 } 785 786 /** 787 * Method to roll back a transaction. 788 * 789 * @param boolean $toSavepoint If true, rollback to the last savepoint. 790 * 791 * @return void 792 * 793 * @since 1.0 794 * @throws \RuntimeException 795 */ 796 public function transactionRollback($toSavepoint = false) 797 { 798 $this->connect(); 799 800 if (!$toSavepoint || $this->transactionDepth <= 1) 801 { 802 parent::transactionRollback($toSavepoint); 803 } 804 else 805 { 806 $savepoint = 'SP_' . ($this->transactionDepth - 1); 807 $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint)); 808 809 if ($this->execute()) 810 { 811 $this->transactionDepth--; 812 } 813 } 814 } 815 816 /** 817 * Method to initialize a transaction. 818 * 819 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. 820 * 821 * @return void 822 * 823 * @since 1.0 824 * @throws \RuntimeException 825 */ 826 public function transactionStart($asSavepoint = false) 827 { 828 $this->connect(); 829 830 if (!$asSavepoint || !$this->transactionDepth) 831 { 832 parent::transactionStart($asSavepoint); 833 } 834 else 835 { 836 $savepoint = 'SP_' . $this->transactionDepth; 837 $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); 838 839 if ($this->execute()) 840 { 841 $this->transactionDepth++; 842 } 843 } 844 } 845 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |