1<?php 2/** 3 * @package Joomla.Platform 4 * @subpackage Database 5 * 6 * @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved. 7 * @license GNU General Public License version 2 or later; see LICENSE 8 */ 9 10defined('JPATH_PLATFORM') or die; 11 12/** 13 * Joomla Platform Database Driver Class 14 * 15 * @since 3.0.0 16 * 17 * @method string|array q() q($text, $escape = true) Alias for quote method 18 * @method string|array qn() qn($name, $as = null) Alias for quoteName method 19 */ 20abstract class JDatabaseDriver extends JDatabase implements JDatabaseInterface 21{ 22 /** 23 * The name of the database. 24 * 25 * @var string 26 * @since 2.5.0 27 */ 28 private $_database; 29 30 /** 31 * The name of the database driver. 32 * 33 * @var string 34 * @since 1.7.0 35 */ 36 public $name; 37 38 /** 39 * The type of the database server family supported by this driver. Examples: mysql, oracle, postgresql, mssql, 40 * sqlite. 41 * 42 * @var string 43 * @since CMS 3.5.0 44 */ 45 public $serverType; 46 47 /** 48 * @var resource The database connection resource. 49 * @since 1.7.0 50 */ 51 protected $connection; 52 53 /** 54 * @var integer The number of SQL statements executed by the database driver. 55 * @since 1.7.0 56 */ 57 protected $count = 0; 58 59 /** 60 * @var resource The database connection cursor from the last query. 61 * @since 1.7.0 62 */ 63 protected $cursor; 64 65 /** 66 * @var boolean The database driver debugging state. 67 * @since 1.7.0 68 */ 69 protected $debug = false; 70 71 /** 72 * @var integer The affected row limit for the current SQL statement. 73 * @since 1.7.0 74 */ 75 protected $limit = 0; 76 77 /** 78 * @var array The log of executed SQL statements by the database driver. 79 * @since 1.7.0 80 */ 81 protected $log = array(); 82 83 /** 84 * @var array The log of executed SQL statements timings (start and stop microtimes) by the database driver. 85 * @since CMS 3.1.2 86 */ 87 protected $timings = array(); 88 89 /** 90 * @var array The log of executed SQL statements timings (start and stop microtimes) by the database driver. 91 * @since CMS 3.1.2 92 */ 93 protected $callStacks = array(); 94 95 /** 96 * @var string The character(s) used to quote SQL statement names such as table names or field names, 97 * etc. The child classes should define this as necessary. If a single character string the 98 * same character is used for both sides of the quoted name, else the first character will be 99 * used for the opening quote and the second for the closing quote. 100 * @since 1.7.0 101 */ 102 protected $nameQuote; 103 104 /** 105 * @var string The null or zero representation of a timestamp for the database driver. This should be 106 * defined in child classes to hold the appropriate value for the engine. 107 * @since 1.7.0 108 */ 109 protected $nullDate; 110 111 /** 112 * @var integer The affected row offset to apply for the current SQL statement. 113 * @since 1.7.0 114 */ 115 protected $offset = 0; 116 117 /** 118 * @var array Passed in upon instantiation and saved. 119 * @since 1.7.0 120 */ 121 protected $options; 122 123 /** 124 * @var JDatabaseQuery|string The current SQL statement to execute. 125 * @since 1.7.0 126 */ 127 protected $sql; 128 129 /** 130 * @var string The common database table prefix. 131 * @since 1.7.0 132 */ 133 protected $tablePrefix; 134 135 /** 136 * @var boolean True if the database engine supports UTF-8 character encoding. 137 * @since 1.7.0 138 */ 139 protected $utf = true; 140 141 /** 142 * @var boolean True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding. 143 * @since CMS 3.5.0 144 */ 145 protected $utf8mb4 = false; 146 147 /** 148 * @var integer The database error number 149 * @since 1.7.0 150 * @deprecated 3.0.0 151 */ 152 protected $errorNum = 0; 153 154 /** 155 * @var string The database error message 156 * @since 1.7.0 157 * @deprecated 3.0.0 158 */ 159 protected $errorMsg; 160 161 /** 162 * @var array JDatabaseDriver instances container. 163 * @since 1.7.0 164 */ 165 protected static $instances = array(); 166 167 /** 168 * @var string The minimum supported database version. 169 * @since 3.0.0 170 */ 171 protected static $dbMinimum; 172 173 /** 174 * @var integer The depth of the current transaction. 175 * @since 3.1.4 176 */ 177 protected $transactionDepth = 0; 178 179 /** 180 * @var callable[] List of callables to call just before disconnecting database 181 * @since CMS 3.1.2 182 */ 183 protected $disconnectHandlers = array(); 184 185 /** 186 * Get a list of available database connectors. The list will only be populated with connectors that both 187 * the class exists and the static test method returns true. This gives us the ability to have a multitude 188 * of connector classes that are self-aware as to whether or not they are able to be used on a given system. 189 * 190 * @return array An array of available database connectors. 191 * 192 * @since 1.7.0 193 */ 194 public static function getConnectors() 195 { 196 $connectors = array(); 197 198 // Get an iterator and loop trough the driver classes. 199 $iterator = new DirectoryIterator(__DIR__ . '/driver'); 200 201 /* @type $file DirectoryIterator */ 202 foreach ($iterator as $file) 203 { 204 $fileName = $file->getFilename(); 205 206 // Only load for php files. 207 if (!$file->isFile() || $file->getExtension() != 'php') 208 { 209 continue; 210 } 211 212 // Derive the class name from the type. 213 $class = str_ireplace('.php', '', 'JDatabaseDriver' . ucfirst(trim($fileName))); 214 215 // If the class doesn't exist we have nothing left to do but look at the next type. We did our best. 216 if (!class_exists($class)) 217 { 218 continue; 219 } 220 221 // Sweet! Our class exists, so now we just need to know if it passes its test method. 222 if ($class::isSupported()) 223 { 224 // Connector names should not have file extensions. 225 $connectors[] = str_ireplace('.php', '', $fileName); 226 } 227 } 228 229 return $connectors; 230 } 231 232 /** 233 * Method to return a JDatabaseDriver instance based on the given options. There are three global options and then 234 * the rest are specific to the database driver. The 'driver' option defines which JDatabaseDriver class is 235 * used for the connection -- the default is 'mysqli'. The 'database' option determines which database is to 236 * be used for the connection. The 'select' option determines whether the connector should automatically select 237 * the chosen database. 238 * 239 * Instances are unique to the given options and new objects are only created when a unique options array is 240 * passed into the method. This ensures that we don't end up with unnecessary database connection resources. 241 * 242 * @param array $options Parameters to be passed to the database driver. 243 * 244 * @return JDatabaseDriver A database object. 245 * 246 * @since 1.7.0 247 * @throws RuntimeException 248 */ 249 public static function getInstance($options = array()) 250 { 251 // Sanitize the database connector options. 252 $options['driver'] = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli'; 253 $options['database'] = (isset($options['database'])) ? $options['database'] : null; 254 $options['select'] = (isset($options['select'])) ? $options['select'] : true; 255 256 // If the selected driver is `mysql` and we are on PHP 7 or greater, switch to the `mysqli` driver. 257 if ($options['driver'] === 'mysql' && PHP_MAJOR_VERSION >= 7) 258 { 259 // Check if we have support for the other MySQL drivers 260 $mysqliSupported = JDatabaseDriverMysqli::isSupported(); 261 $pdoMysqlSupported = JDatabaseDriverPdomysql::isSupported(); 262 263 // If neither is supported, then the user cannot use MySQL; throw an exception 264 if (!$mysqliSupported && !$pdoMysqlSupported) 265 { 266 throw new JDatabaseExceptionUnsupported( 267 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.' 268 . ' Also, this system does not support MySQLi or PDO MySQL. Cannot instantiate database driver.' 269 ); 270 } 271 272 // Prefer MySQLi as it is a closer replacement for the removed MySQL driver, otherwise use the PDO driver 273 if ($mysqliSupported) 274 { 275 JLog::add( 276 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `mysqli` instead.', 277 JLog::WARNING, 278 'deprecated' 279 ); 280 281 $options['driver'] = 'mysqli'; 282 } 283 else 284 { 285 JLog::add( 286 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `pdomysql` instead.', 287 JLog::WARNING, 288 'deprecated' 289 ); 290 291 $options['driver'] = 'pdomysql'; 292 } 293 } 294 295 // Get the options signature for the database connector. 296 $signature = md5(serialize($options)); 297 298 // If we already have a database connector instance for these options then just use that. 299 if (empty(self::$instances[$signature])) 300 { 301 // Derive the class name from the driver. 302 $class = 'JDatabaseDriver' . ucfirst(strtolower($options['driver'])); 303 304 // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best. 305 if (!class_exists($class)) 306 { 307 throw new JDatabaseExceptionUnsupported(sprintf('Unable to load Database Driver: %s', $options['driver'])); 308 } 309 310 // Create our new JDatabaseDriver connector based on the options given. 311 try 312 { 313 $instance = new $class($options); 314 } 315 catch (RuntimeException $e) 316 { 317 throw new JDatabaseExceptionConnecting(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e); 318 } 319 320 // Set the new connector to the global instances based on signature. 321 self::$instances[$signature] = $instance; 322 } 323 324 return self::$instances[$signature]; 325 } 326 327 /** 328 * Splits a string of multiple queries into an array of individual queries. 329 * Single line or line end comments and multi line comments are stripped off. 330 * 331 * @param string $sql Input SQL string with which to split into individual queries. 332 * 333 * @return array The queries from the input string separated into an array. 334 * 335 * @since 1.7.0 336 */ 337 public static function splitSql($sql) 338 { 339 $start = 0; 340 $open = false; 341 $comment = false; 342 $endString = ''; 343 $end = strlen($sql); 344 $queries = array(); 345 $query = ''; 346 347 for ($i = 0; $i < $end; $i++) 348 { 349 $current = substr($sql, $i, 1); 350 $current2 = substr($sql, $i, 2); 351 $current3 = substr($sql, $i, 3); 352 $lenEndString = strlen($endString); 353 $testEnd = substr($sql, $i, $lenEndString); 354 355 if ($current == '"' || $current == "'" || $current2 == '--' 356 || ($current2 == '/*' && $current3 != '/*!' && $current3 != '/*+') 357 || ($current == '#' && $current3 != '#__') 358 || ($comment && $testEnd == $endString)) 359 { 360 // Check if quoted with previous backslash 361 $n = 2; 362 363 while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i) 364 { 365 $n++; 366 } 367 368 // Not quoted 369 if ($n % 2 == 0) 370 { 371 if ($open) 372 { 373 if ($testEnd == $endString) 374 { 375 if ($comment) 376 { 377 $comment = false; 378 if ($lenEndString > 1) 379 { 380 $i += ($lenEndString - 1); 381 $current = substr($sql, $i, 1); 382 } 383 $start = $i + 1; 384 } 385 $open = false; 386 $endString = ''; 387 } 388 } 389 else 390 { 391 $open = true; 392 if ($current2 == '--') 393 { 394 $endString = "\n"; 395 $comment = true; 396 } 397 elseif ($current2 == '/*') 398 { 399 $endString = '*/'; 400 $comment = true; 401 } 402 elseif ($current == '#') 403 { 404 $endString = "\n"; 405 $comment = true; 406 } 407 else 408 { 409 $endString = $current; 410 } 411 if ($comment && $start < $i) 412 { 413 $query = $query . substr($sql, $start, ($i - $start)); 414 } 415 } 416 } 417 } 418 419 if ($comment) 420 { 421 $start = $i + 1; 422 } 423 424 if (($current == ';' && !$open) || $i == $end - 1) 425 { 426 if ($start <= $i) 427 { 428 $query = $query . substr($sql, $start, ($i - $start + 1)); 429 } 430 $query = trim($query); 431 432 if ($query) 433 { 434 if (($i == $end - 1) && ($current != ';')) 435 { 436 $query = $query . ';'; 437 } 438 $queries[] = $query; 439 } 440 441 $query = ''; 442 $start = $i + 1; 443 } 444 } 445 446 return $queries; 447 } 448 449 /** 450 * Magic method to provide method alias support for quote() and quoteName(). 451 * 452 * @param string $method The called method. 453 * @param array $args The array of arguments passed to the method. 454 * 455 * @return mixed The aliased method's return value or null. 456 * 457 * @since 1.7.0 458 */ 459 public function __call($method, $args) 460 { 461 if (empty($args)) 462 { 463 return; 464 } 465 466 switch ($method) 467 { 468 case 'q': 469 return $this->quote($args[0], isset($args[1]) ? $args[1] : true); 470 break; 471 case 'qn': 472 return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null); 473 break; 474 } 475 } 476 477 /** 478 * Constructor. 479 * 480 * @param array $options List of options used to configure the connection 481 * 482 * @since 1.7.0 483 */ 484 public function __construct($options) 485 { 486 // Initialise object variables. 487 $this->_database = (isset($options['database'])) ? $options['database'] : ''; 488 489 $this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_'; 490 $this->count = 0; 491 $this->errorNum = 0; 492 $this->log = array(); 493 494 // Set class options. 495 $this->options = $options; 496 } 497 498 /** 499 * Alter database's character set, obtaining query string from protected member. 500 * 501 * @param string $dbName The database name that will be altered 502 * 503 * @return string The query that alter the database query string 504 * 505 * @since 3.0.1 506 * @throws RuntimeException 507 */ 508 public function alterDbCharacterSet($dbName) 509 { 510 if (is_null($dbName)) 511 { 512 throw new RuntimeException('Database name must not be null.'); 513 } 514 515 $this->setQuery($this->getAlterDbCharacterSet($dbName)); 516 517 return $this->execute(); 518 } 519 520 /** 521 * Alter a table's character set, obtaining an array of queries to do so from a protected method. The conversion is 522 * wrapped in a transaction, if supported by the database driver. Otherwise the table will be locked before the 523 * conversion. This prevents data corruption. 524 * 525 * @param string $tableName The name of the table to alter 526 * @param boolean $rethrow True to rethrow database exceptions. Default: false (exceptions are suppressed) 527 * 528 * @return boolean True if successful 529 * 530 * @since CMS 3.5.0 531 * @throws RuntimeException If the table name is empty 532 * @throws Exception Relayed from the database layer if a database error occurs and $rethrow == true 533 */ 534 public function alterTableCharacterSet($tableName, $rethrow = false) 535 { 536 if (is_null($tableName)) 537 { 538 throw new RuntimeException('Table name must not be null.'); 539 } 540 541 $queries = $this->getAlterTableCharacterSet($tableName); 542 543 if (empty($queries)) 544 { 545 return false; 546 } 547 548 $hasTransaction = true; 549 550 try 551 { 552 $this->transactionStart(); 553 } 554 catch (Exception $e) 555 { 556 $hasTransaction = false; 557 $this->lockTable($tableName); 558 } 559 560 foreach ($queries as $query) 561 { 562 try 563 { 564 $this->setQuery($query)->execute(); 565 } 566 catch (Exception $e) 567 { 568 if ($hasTransaction) 569 { 570 $this->transactionRollback(); 571 } 572 else 573 { 574 $this->unlockTables(); 575 } 576 577 if ($rethrow) 578 { 579 throw $e; 580 } 581 582 return false; 583 } 584 } 585 586 if ($hasTransaction) 587 { 588 try 589 { 590 $this->transactionCommit(); 591 } 592 catch (Exception $e) 593 { 594 $this->transactionRollback(); 595 596 if ($rethrow) 597 { 598 throw $e; 599 } 600 601 return false; 602 } 603 } 604 else 605 { 606 $this->unlockTables(); 607 } 608 609 return true; 610 } 611 612 /** 613 * Connects to the database if needed. 614 * 615 * @return void Returns void if the database connected successfully. 616 * 617 * @since 3.0.0 618 * @throws RuntimeException 619 */ 620 abstract public function connect(); 621 622 /** 623 * Determines if the connection to the server is active. 624 * 625 * @return boolean True if connected to the database engine. 626 * 627 * @since 1.7.0 628 */ 629 abstract public function connected(); 630 631 /** 632 * Create a new database using information from $options object, obtaining query string 633 * from protected member. 634 * 635 * @param stdClass $options Object used to pass user and database name to database driver. 636 * This object must have "db_name" and "db_user" set. 637 * @param boolean $utf True if the database supports the UTF-8 character set. 638 * 639 * @return string The query that creates database 640 * 641 * @since 3.0.1 642 * @throws RuntimeException 643 */ 644 public function createDatabase($options, $utf = true) 645 { 646 if (is_null($options)) 647 { 648 throw new RuntimeException('$options object must not be null.'); 649 } 650 elseif (empty($options->db_name)) 651 { 652 throw new RuntimeException('$options object must have db_name set.'); 653 } 654 elseif (empty($options->db_user)) 655 { 656 throw new RuntimeException('$options object must have db_user set.'); 657 } 658 659 $this->setQuery($this->getCreateDatabaseQuery($options, $utf)); 660 661 return $this->execute(); 662 } 663 664 /** 665 * Destructor. 666 * 667 * @since 3.8.0 668 */ 669 public function __destruct() 670 { 671 $this->disconnect(); 672 } 673 674 /** 675 * Disconnects the database. 676 * 677 * @return void 678 * 679 * @since 3.0.0 680 */ 681 abstract public function disconnect(); 682 683 /** 684 * Adds a function callable just before disconnecting the database. Parameter of the callable is $this JDatabaseDriver 685 * 686 * @param callable $callable Function to call in disconnect() method just before disconnecting from database 687 * 688 * @return void 689 * 690 * @since CMS 3.1.2 691 */ 692 public function addDisconnectHandler($callable) 693 { 694 $this->disconnectHandlers[] = $callable; 695 } 696 697 /** 698 * Drops a table from the database. 699 * 700 * @param string $table The name of the database table to drop. 701 * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. 702 * 703 * @return JDatabaseDriver Returns this object to support chaining. 704 * 705 * @since 2.5.0 706 * @throws RuntimeException 707 */ 708 abstract public function dropTable($table, $ifExists = true); 709 710 /** 711 * Escapes a string for usage in an SQL statement. 712 * 713 * @param string $text The string to be escaped. 714 * @param boolean $extra Optional parameter to provide extra escaping. 715 * 716 * @return string The escaped string. 717 * 718 * @since 1.7.0 719 */ 720 abstract public function escape($text, $extra = false); 721 722 /** 723 * Method to fetch a row from the result set cursor as an array. 724 * 725 * @param mixed $cursor The optional result set cursor from which to fetch the row. 726 * 727 * @return mixed Either the next row from the result set or false if there are no more rows. 728 * 729 * @since 1.7.0 730 */ 731 abstract protected function fetchArray($cursor = null); 732 733 /** 734 * Method to fetch a row from the result set cursor as an associative array. 735 * 736 * @param mixed $cursor The optional result set cursor from which to fetch the row. 737 * 738 * @return mixed Either the next row from the result set or false if there are no more rows. 739 * 740 * @since 1.7.0 741 */ 742 abstract protected function fetchAssoc($cursor = null); 743 744 /** 745 * Method to fetch a row from the result set cursor as an object. 746 * 747 * @param mixed $cursor The optional result set cursor from which to fetch the row. 748 * @param string $class The class name to use for the returned row object. 749 * 750 * @return mixed Either the next row from the result set or false if there are no more rows. 751 * 752 * @since 1.7.0 753 */ 754 abstract protected function fetchObject($cursor = null, $class = 'stdClass'); 755 756 /** 757 * Method to free up the memory used for the result set. 758 * 759 * @param mixed $cursor The optional result set cursor from which to fetch the row. 760 * 761 * @return void 762 * 763 * @since 1.7.0 764 */ 765 abstract protected function freeResult($cursor = null); 766 767 /** 768 * Get the number of affected rows for the previous executed SQL statement. 769 * 770 * @return integer The number of affected rows. 771 * 772 * @since 1.7.0 773 */ 774 abstract public function getAffectedRows(); 775 776 /** 777 * Return the query string to alter the database character set. 778 * 779 * @param string $dbName The database name 780 * 781 * @return string The query that alter the database query string 782 * 783 * @since 3.0.1 784 */ 785 public function getAlterDbCharacterSet($dbName) 786 { 787 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; 788 789 return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`'; 790 } 791 792 /** 793 * Get the query strings to alter the character set and collation of a table. 794 * 795 * @param string $tableName The name of the table 796 * 797 * @return string[] The queries required to alter the table's character set and collation 798 * 799 * @since CMS 3.5.0 800 */ 801 public function getAlterTableCharacterSet($tableName) 802 { 803 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; 804 $collation = $charset . '_unicode_ci'; 805 806 $quotedTableName = $this->quoteName($tableName); 807 808 $queries = array(); 809 $queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation"; 810 811 /** 812 * We also need to convert each text column, modifying their character set and collation. This allows us to 813 * change, for example, a utf8_bin collated column to a utf8mb4_bin collated column. 814 */ 815 $sql = "SHOW FULL COLUMNS FROM $quotedTableName"; 816 $this->setQuery($sql); 817 $columns = $this->loadAssocList(); 818 $columnMods = array(); 819 820 if (is_array($columns)) 821 { 822 foreach ($columns as $column) 823 { 824 // Make sure we are redefining only columns which do support a collation 825 $col = (object) $column; 826 827 if (empty($col->Collation)) 828 { 829 continue; 830 } 831 832 // Default new collation: utf8_unicode_ci or utf8mb4_unicode_ci 833 $newCollation = $charset . '_unicode_ci'; 834 $collationParts = explode('_', $col->Collation); 835 836 /** 837 * If the collation is in the form charset_collationType_ci or charset_collationType we have to change 838 * the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT 839 * utf8mb4_general_ci). 840 */ 841 if (count($collationParts) >= 2) 842 { 843 $ci = array_pop($collationParts); 844 $collationType = array_pop($collationParts); 845 $newCollation = $charset . '_' . $collationType . '_' . $ci; 846 847 /** 848 * When the last part of the old collation is not _ci we have a charset_collationType format, 849 * something like utf8_bin. Therefore the new collation only has *two* parts. 850 */ 851 if ($ci != 'ci') 852 { 853 $newCollation = $charset . '_' . $ci; 854 } 855 } 856 857 // If the old and new collation is the same we don't have to change the collation type 858 if (strtolower($newCollation) == strtolower($col->Collation)) 859 { 860 continue; 861 } 862 863 $null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL'; 864 $default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'"; 865 $columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default"; 866 } 867 } 868 869 if (count($columnMods)) 870 { 871 $queries[] = "ALTER TABLE $quotedTableName " . 872 implode(',', $columnMods) . 873 " CHARACTER SET $charset COLLATE $collation"; 874 } 875 876 return $queries; 877 } 878 879 /** 880 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used 881 * when the server doesn't support UTF-8 Multibyte. 882 * 883 * @param string $query The query to convert 884 * 885 * @return string The converted query 886 */ 887 public function convertUtf8mb4QueryToUtf8($query) 888 { 889 if ($this->hasUTF8mb4Support()) 890 { 891 return $query; 892 } 893 894 // If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert 895 if (!preg_match('/^(ALTER|CREATE)\s+TABLE\s+/i', $query)) 896 { 897 return $query; 898 } 899 900 // Don't do preg replacement if string does not exist 901 if (stripos($query, 'utf8mb4') === false) 902 { 903 return $query; 904 } 905 906 // Replace utf8mb4 with utf8 if not within single or double quotes or name quotes 907 return preg_replace('/[`"\'][^`"\']*[`"\'](*SKIP)(*FAIL)|utf8mb4/i', 'utf8', $query); 908 } 909 910 /** 911 * Return the query string to create new Database. 912 * Each database driver, other than MySQL, need to override this member to return correct string. 913 * 914 * @param stdClass $options Object used to pass user and database name to database driver. 915 * This object must have "db_name" and "db_user" set. 916 * @param boolean $utf True if the database supports the UTF-8 character set. 917 * 918 * @return string The query that creates database 919 * 920 * @since 3.0.1 921 */ 922 protected function getCreateDatabaseQuery($options, $utf) 923 { 924 if ($utf) 925 { 926 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; 927 $collation = $charset . '_unicode_ci'; 928 929 return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`'; 930 } 931 932 return 'CREATE DATABASE ' . $this->quoteName($options->db_name); 933 } 934 935 /** 936 * Method to get the database collation in use by sampling a text field of a table in the database. 937 * 938 * @return mixed The collation in use by the database or boolean false if not supported. 939 * 940 * @since 1.7.0 941 */ 942 abstract public function getCollation(); 943 944 /** 945 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support 946 * reporting this value please return an empty string. 947 * 948 * @return string 949 */ 950 public function getConnectionCollation() 951 { 952 return ''; 953 } 954 955 /** 956 * Method that provides access to the underlying database connection. Useful for when you need to call a 957 * proprietary method such as postgresql's lo_* methods. 958 * 959 * @return resource The underlying database connection resource. 960 * 961 * @since 1.7.0 962 */ 963 public function getConnection() 964 { 965 return $this->connection; 966 } 967 968 /** 969 * Get the total number of SQL statements executed by the database driver. 970 * 971 * @return integer 972 * 973 * @since 1.7.0 974 */ 975 public function getCount() 976 { 977 return $this->count; 978 } 979 980 /** 981 * Gets the name of the database used by this connection. 982 * 983 * @return string 984 * 985 * @since 2.5.0 986 */ 987 protected function getDatabase() 988 { 989 return $this->_database; 990 } 991 992 /** 993 * Returns a PHP date() function compliant date format for the database driver. 994 * 995 * @return string The format string. 996 * 997 * @since 1.7.0 998 */ 999 public function getDateFormat() 1000 { 1001 return 'Y-m-d H:i:s'; 1002 } 1003 1004 /** 1005 * Get the database driver SQL statement log. 1006 * 1007 * @return array SQL statements executed by the database driver. 1008 * 1009 * @since 1.7.0 1010 */ 1011 public function getLog() 1012 { 1013 return $this->log; 1014 } 1015 1016 /** 1017 * Get the database driver SQL statement log. 1018 * 1019 * @return array SQL statements executed by the database driver. 1020 * 1021 * @since CMS 3.1.2 1022 */ 1023 public function getTimings() 1024 { 1025 return $this->timings; 1026 } 1027 1028 /** 1029 * Get the database driver SQL statement log. 1030 * 1031 * @return array SQL statements executed by the database driver. 1032 * 1033 * @since CMS 3.1.2 1034 */ 1035 public function getCallStacks() 1036 { 1037 return $this->callStacks; 1038 } 1039 1040 /** 1041 * Get the minimum supported database version. 1042 * 1043 * @return string The minimum version number for the database driver. 1044 * 1045 * @since 3.0.0 1046 */ 1047 public function getMinimum() 1048 { 1049 return static::$dbMinimum; 1050 } 1051 1052 /** 1053 * Get the null or zero representation of a timestamp for the database driver. 1054 * 1055 * @return string Null or zero representation of a timestamp. 1056 * 1057 * @since 1.7.0 1058 */ 1059 public function getNullDate() 1060 { 1061 return $this->nullDate; 1062 } 1063 1064 /** 1065 * Get the number of returned rows for the previous executed SQL statement. 1066 * 1067 * @param resource $cursor An optional database cursor resource to extract the row count from. 1068 * 1069 * @return integer The number of returned rows. 1070 * 1071 * @since 1.7.0 1072 */ 1073 abstract public function getNumRows($cursor = null); 1074 1075 /** 1076 * Get the common table prefix for the database driver. 1077 * 1078 * @return string The common database table prefix. 1079 * 1080 * @since 1.7.0 1081 */ 1082 public function getPrefix() 1083 { 1084 return $this->tablePrefix; 1085 } 1086 1087 /** 1088 * Gets an exporter class object. 1089 * 1090 * @return JDatabaseExporter An exporter object. 1091 * 1092 * @since 3.0.0 1093 * @throws RuntimeException 1094 */ 1095 public function getExporter() 1096 { 1097 // Derive the class name from the driver. 1098 $class = 'JDatabaseExporter' . ucfirst($this->name); 1099 1100 // Make sure we have an exporter class for this driver. 1101 if (!class_exists($class)) 1102 { 1103 // If it doesn't exist we are at an impasse so throw an exception. 1104 throw new JDatabaseExceptionUnsupported('Database Exporter not found.'); 1105 } 1106 1107 $o = new $class; 1108 $o->setDbo($this); 1109 1110 return $o; 1111 } 1112 1113 /** 1114 * Gets an importer class object. 1115 * 1116 * @return JDatabaseImporter An importer object. 1117 * 1118 * @since 3.0.0 1119 * @throws RuntimeException 1120 */ 1121 public function getImporter() 1122 { 1123 // Derive the class name from the driver. 1124 $class = 'JDatabaseImporter' . ucfirst($this->name); 1125 1126 // Make sure we have an importer class for this driver. 1127 if (!class_exists($class)) 1128 { 1129 // If it doesn't exist we are at an impasse so throw an exception. 1130 throw new JDatabaseExceptionUnsupported('Database Importer not found'); 1131 } 1132 1133 $o = new $class; 1134 $o->setDbo($this); 1135 1136 return $o; 1137 } 1138 1139 /** 1140 * Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the 1141 * class name. 1142 * 1143 * @return string 1144 * 1145 * @since CMS 3.5.0 1146 */ 1147 public function getName() 1148 { 1149 if (empty($this->name)) 1150 { 1151 $className = get_class($this); 1152 $className = str_replace('JDatabaseDriver', '', $className); 1153 $this->name = strtolower($className); 1154 } 1155 1156 return $this->name; 1157 } 1158 1159 /** 1160 * Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it 1161 * will attempt guessing the server family type from the driver name. If this is not possible the driver name will 1162 * be returned instead. 1163 * 1164 * @return string 1165 * 1166 * @since CMS 3.5.0 1167 */ 1168 public function getServerType() 1169 { 1170 if (empty($this->serverType)) 1171 { 1172 $name = $this->getName(); 1173 1174 if (stristr($name, 'mysql') !== false) 1175 { 1176 $this->serverType = 'mysql'; 1177 } 1178 elseif (stristr($name, 'postgre') !== false) 1179 { 1180 $this->serverType = 'postgresql'; 1181 } 1182 elseif (stristr($name, 'pgsql') !== false) 1183 { 1184 $this->serverType = 'postgresql'; 1185 } 1186 elseif (stristr($name, 'oracle') !== false) 1187 { 1188 $this->serverType = 'oracle'; 1189 } 1190 elseif (stristr($name, 'sqlite') !== false) 1191 { 1192 $this->serverType = 'sqlite'; 1193 } 1194 elseif (stristr($name, 'sqlsrv') !== false) 1195 { 1196 $this->serverType = 'mssql'; 1197 } 1198 elseif (stristr($name, 'mssql') !== false) 1199 { 1200 $this->serverType = 'mssql'; 1201 } 1202 else 1203 { 1204 $this->serverType = $name; 1205 } 1206 } 1207 1208 return $this->serverType; 1209 } 1210 1211 /** 1212 * Get the current query object or a new JDatabaseQuery object. 1213 * 1214 * @param boolean $new False to return the current query object, True to return a new JDatabaseQuery object. 1215 * 1216 * @return JDatabaseQuery The current query object or a new object extending the JDatabaseQuery class. 1217 * 1218 * @since 1.7.0 1219 * @throws RuntimeException 1220 */ 1221 public function getQuery($new = false) 1222 { 1223 if ($new) 1224 { 1225 // Derive the class name from the driver. 1226 $class = 'JDatabaseQuery' . ucfirst($this->name); 1227 1228 // Make sure we have a query class for this driver. 1229 if (!class_exists($class)) 1230 { 1231 // If it doesn't exist we are at an impasse so throw an exception. 1232 throw new JDatabaseExceptionUnsupported('Database Query Class not found.'); 1233 } 1234 1235 return new $class($this); 1236 } 1237 else 1238 { 1239 return $this->sql; 1240 } 1241 } 1242 1243 /** 1244 * Get a new iterator on the current query. 1245 * 1246 * @param string $column An option column to use as the iterator key. 1247 * @param string $class The class of object that is returned. 1248 * 1249 * @return JDatabaseIterator A new database iterator. 1250 * 1251 * @since 3.0.0 1252 * @throws RuntimeException 1253 */ 1254 public function getIterator($column = null, $class = 'stdClass') 1255 { 1256 // Derive the class name from the driver. 1257 $iteratorClass = 'JDatabaseIterator' . ucfirst($this->name); 1258 1259 // Make sure we have an iterator class for this driver. 1260 if (!class_exists($iteratorClass)) 1261 { 1262 // If it doesn't exist we are at an impasse so throw an exception. 1263 throw new JDatabaseExceptionUnsupported(sprintf('class *%s* is not defined', $iteratorClass)); 1264 } 1265 1266 // Return a new iterator 1267 return new $iteratorClass($this->execute(), $column, $class); 1268 } 1269 1270 /** 1271 * Retrieves field information about the given tables. 1272 * 1273 * @param string $table The name of the database table. 1274 * @param boolean $typeOnly True (default) to only return field types. 1275 * 1276 * @return array An array of fields by table. 1277 * 1278 * @since 1.7.0 1279 * @throws RuntimeException 1280 */ 1281 abstract public function getTableColumns($table, $typeOnly = true); 1282 1283 /** 1284 * Shows the table CREATE statement that creates the given tables. 1285 * 1286 * @param mixed $tables A table name or a list of table names. 1287 * 1288 * @return array A list of the create SQL for the tables. 1289 * 1290 * @since 1.7.0 1291 * @throws RuntimeException 1292 */ 1293 abstract public function getTableCreate($tables); 1294 1295 /** 1296 * Retrieves keys information about the given table. 1297 * 1298 * @param string $table The name of the table. 1299 * 1300 * @return array An array of keys for the table. 1301 * 1302 * @since 1.7.0 1303 * @throws RuntimeException 1304 */ 1305 abstract public function getTableKeys($table); 1306 1307 /** 1308 * Method to get an array of all tables in the database. 1309 * 1310 * @return array An array of all the tables in the database. 1311 * 1312 * @since 1.7.0 1313 * @throws RuntimeException 1314 */ 1315 abstract public function getTableList(); 1316 1317 /** 1318 * Determine whether or not the database engine supports UTF-8 character encoding. 1319 * 1320 * @return boolean True if the database engine supports UTF-8 character encoding. 1321 * 1322 * @since 1.7.0 1323 * @deprecated 4.0 - Use hasUTFSupport() instead 1324 */ 1325 public function getUTFSupport() 1326 { 1327 JLog::add('JDatabaseDriver::getUTFSupport() is deprecated. Use JDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated'); 1328 1329 return $this->hasUTFSupport(); 1330 } 1331 1332 /** 1333 * Determine whether or not the database engine supports UTF-8 character encoding. 1334 * 1335 * @return boolean True if the database engine supports UTF-8 character encoding. 1336 * 1337 * @since 3.0.0 1338 */ 1339 public function hasUTFSupport() 1340 { 1341 return $this->utf; 1342 } 1343 1344 /** 1345 * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to 1346 * MySQL databases. 1347 * 1348 * @return boolean True if the database engine supports UTF-8 Multibyte. 1349 * 1350 * @since CMS 3.5.0 1351 */ 1352 public function hasUTF8mb4Support() 1353 { 1354 return $this->utf8mb4; 1355 } 1356 1357 /** 1358 * Get the version of the database connector 1359 * 1360 * @return string The database connector version. 1361 * 1362 * @since 1.7.0 1363 */ 1364 abstract public function getVersion(); 1365 1366 /** 1367 * Method to get the auto-incremented value from the last INSERT statement. 1368 * 1369 * @return mixed The value of the auto-increment field from the last inserted row. 1370 * 1371 * @since 1.7.0 1372 */ 1373 abstract public function insertid(); 1374 1375 /** 1376 * Inserts a row into a table based on an object's properties. 1377 * 1378 * @param string $table The name of the database table to insert into. 1379 * @param object &$object A reference to an object whose public properties match the table fields. 1380 * @param string $key The name of the primary key. If provided the object property is updated. 1381 * 1382 * @return boolean True on success. 1383 * 1384 * @since 1.7.0 1385 * @throws RuntimeException 1386 */ 1387 public function insertObject($table, &$object, $key = null) 1388 { 1389 $fields = array(); 1390 $values = array(); 1391 1392 // Iterate over the object variables to build the query fields and values. 1393 foreach (get_object_vars($object) as $k => $v) 1394 { 1395 // Only process non-null scalars. 1396 if (is_array($v) or is_object($v) or $v === null) 1397 { 1398 continue; 1399 } 1400 1401 // Ignore any internal fields. 1402 if ($k[0] == '_') 1403 { 1404 continue; 1405 } 1406 1407 // Prepare and sanitize the fields and values for the database query. 1408 $fields[] = $this->quoteName($k); 1409 $values[] = $this->quote($v); 1410 } 1411 1412 // Create the base insert statement. 1413 $query = $this->getQuery(true) 1414 ->insert($this->quoteName($table)) 1415 ->columns($fields) 1416 ->values(implode(',', $values)); 1417 1418 // Set the query and execute the insert. 1419 $this->setQuery($query); 1420 1421 if (!$this->execute()) 1422 { 1423 return false; 1424 } 1425 1426 // Update the primary key if it exists. 1427 $id = $this->insertid(); 1428 1429 if ($key && $id && is_string($key)) 1430 { 1431 $object->$key = $id; 1432 } 1433 1434 return true; 1435 } 1436 1437 /** 1438 * Method to check whether the installed database version is supported by the database driver 1439 * 1440 * @return boolean True if the database version is supported 1441 * 1442 * @since 3.0.0 1443 */ 1444 public function isMinimumVersion() 1445 { 1446 return version_compare($this->getVersion(), static::$dbMinimum) >= 0; 1447 } 1448 1449 /** 1450 * Method to get the first row of the result set from the database query as an associative array 1451 * of ['field_name' => 'row_value']. 1452 * 1453 * @return mixed The return value or null if the query failed. 1454 * 1455 * @since 1.7.0 1456 * @throws RuntimeException 1457 */ 1458 public function loadAssoc() 1459 { 1460 $this->connect(); 1461 1462 $ret = null; 1463 1464 // Execute the query and get the result set cursor. 1465 if (!($cursor = $this->execute())) 1466 { 1467 return; 1468 } 1469 1470 // Get the first row from the result set as an associative array. 1471 if ($array = $this->fetchAssoc($cursor)) 1472 { 1473 $ret = $array; 1474 } 1475 1476 // Free up system resources and return. 1477 $this->freeResult($cursor); 1478 1479 return $ret; 1480 } 1481 1482 /** 1483 * Method to get an array of the result set rows from the database query where each row is an associative array 1484 * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to 1485 * a sequential numeric array. 1486 * 1487 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted 1488 * behavior and should be avoided. 1489 * 1490 * @param string $key The name of a field on which to key the result array. 1491 * @param string $column An optional column name. Instead of the whole row, only this column value will be in 1492 * the result array. 1493 * 1494 * @return mixed The return value or null if the query failed. 1495 * 1496 * @since 1.7.0 1497 * @throws RuntimeException 1498 */ 1499 public function loadAssocList($key = null, $column = null) 1500 { 1501 $this->connect(); 1502 1503 $array = array(); 1504 1505 // Execute the query and get the result set cursor. 1506 if (!($cursor = $this->execute())) 1507 { 1508 return; 1509 } 1510 1511 // Get all of the rows from the result set. 1512 while ($row = $this->fetchAssoc($cursor)) 1513 { 1514 $value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row; 1515 1516 if ($key) 1517 { 1518 $array[$row[$key]] = $value; 1519 } 1520 else 1521 { 1522 $array[] = $value; 1523 } 1524 } 1525 1526 // Free up system resources and return. 1527 $this->freeResult($cursor); 1528 1529 return $array; 1530 } 1531 1532 /** 1533 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from 1534 * the database query. 1535 * 1536 * @param integer $offset The row offset to use to build the result array. 1537 * 1538 * @return mixed The return value or null if the query failed. 1539 * 1540 * @since 1.7.0 1541 * @throws RuntimeException 1542 */ 1543 public function loadColumn($offset = 0) 1544 { 1545 $this->connect(); 1546 1547 $array = array(); 1548 1549 // Execute the query and get the result set cursor. 1550 if (!($cursor = $this->execute())) 1551 { 1552 return; 1553 } 1554 1555 // Get all of the rows from the result set as arrays. 1556 while ($row = $this->fetchArray($cursor)) 1557 { 1558 $array[] = $row[$offset]; 1559 } 1560 1561 // Free up system resources and return. 1562 $this->freeResult($cursor); 1563 1564 return $array; 1565 } 1566 1567 /** 1568 * Method to get the next row in the result set from the database query as an object. 1569 * 1570 * @param string $class The class name to use for the returned row object. 1571 * 1572 * @return mixed The result of the query as an array, false if there are no more rows. 1573 * 1574 * @since 1.7.0 1575 * @throws RuntimeException 1576 * @deprecated 4.0 - Use getIterator() instead 1577 */ 1578 public function loadNextObject($class = 'stdClass') 1579 { 1580 JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated'); 1581 $this->connect(); 1582 1583 static $cursor = null; 1584 1585 // Execute the query and get the result set cursor. 1586 if (is_null($cursor)) 1587 { 1588 if (!($cursor = $this->execute())) 1589 { 1590 return $this->errorNum ? null : false; 1591 } 1592 } 1593 1594 // Get the next row from the result set as an object of type $class. 1595 if ($row = $this->fetchObject($cursor, $class)) 1596 { 1597 return $row; 1598 } 1599 1600 // Free up system resources and return. 1601 $this->freeResult($cursor); 1602 $cursor = null; 1603 1604 return false; 1605 } 1606 1607 /** 1608 * Method to get the next row in the result set from the database query as an array. 1609 * 1610 * @return mixed The result of the query as an array, false if there are no more rows. 1611 * 1612 * @since 1.7.0 1613 * @throws RuntimeException 1614 * @deprecated 4.0 (CMS) Use JDatabaseDriver::getIterator() instead 1615 */ 1616 public function loadNextRow() 1617 { 1618 JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated'); 1619 $this->connect(); 1620 1621 static $cursor = null; 1622 1623 // Execute the query and get the result set cursor. 1624 if (is_null($cursor)) 1625 { 1626 if (!($cursor = $this->execute())) 1627 { 1628 return $this->errorNum ? null : false; 1629 } 1630 } 1631 1632 // Get the next row from the result set as an object of type $class. 1633 if ($row = $this->fetchArray($cursor)) 1634 { 1635 return $row; 1636 } 1637 1638 // Free up system resources and return. 1639 $this->freeResult($cursor); 1640 $cursor = null; 1641 1642 return false; 1643 } 1644 1645 /** 1646 * Method to get the first row of the result set from the database query as an object. 1647 * 1648 * @param string $class The class name to use for the returned row object. 1649 * 1650 * @return mixed The return value or null if the query failed. 1651 * 1652 * @since 1.7.0 1653 * @throws RuntimeException 1654 */ 1655 public function loadObject($class = 'stdClass') 1656 { 1657 $this->connect(); 1658 1659 $ret = null; 1660 1661 // Execute the query and get the result set cursor. 1662 if (!($cursor = $this->execute())) 1663 { 1664 return; 1665 } 1666 1667 // Get the first row from the result set as an object of type $class. 1668 if ($object = $this->fetchObject($cursor, $class)) 1669 { 1670 $ret = $object; 1671 } 1672 1673 // Free up system resources and return. 1674 $this->freeResult($cursor); 1675 1676 return $ret; 1677 } 1678 1679 /** 1680 * Method to get an array of the result set rows from the database query where each row is an object. The array 1681 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array. 1682 * 1683 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted 1684 * behavior and should be avoided. 1685 * 1686 * @param string $key The name of a field on which to key the result array. 1687 * @param string $class The class name to use for the returned row objects. 1688 * 1689 * @return mixed The return value or null if the query failed. 1690 * 1691 * @since 1.7.0 1692 * @throws RuntimeException 1693 */ 1694 public function loadObjectList($key = '', $class = 'stdClass') 1695 { 1696 $this->connect(); 1697 1698 $array = array(); 1699 1700 // Execute the query and get the result set cursor. 1701 if (!($cursor = $this->execute())) 1702 { 1703 return; 1704 } 1705 1706 // Get all of the rows from the result set as objects of type $class. 1707 while ($row = $this->fetchObject($cursor, $class)) 1708 { 1709 if ($key) 1710 { 1711 $array[$row->$key] = $row; 1712 } 1713 else 1714 { 1715 $array[] = $row; 1716 } 1717 } 1718 1719 // Free up system resources and return. 1720 $this->freeResult($cursor); 1721 1722 return $array; 1723 } 1724 1725 /** 1726 * Method to get the first field of the first row of the result set from the database query. 1727 * 1728 * @return mixed The return value or null if the query failed. 1729 * 1730 * @since 1.7.0 1731 * @throws RuntimeException 1732 */ 1733 public function loadResult() 1734 { 1735 $this->connect(); 1736 1737 $ret = null; 1738 1739 // Execute the query and get the result set cursor. 1740 if (!($cursor = $this->execute())) 1741 { 1742 return; 1743 } 1744 1745 // Get the first row from the result set as an array. 1746 if ($row = $this->fetchArray($cursor)) 1747 { 1748 $ret = $row[0]; 1749 } 1750 1751 // Free up system resources and return. 1752 $this->freeResult($cursor); 1753 1754 return $ret; 1755 } 1756 1757 /** 1758 * Method to get the first row of the result set from the database query as an array. Columns are indexed 1759 * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc. 1760 * 1761 * @return mixed The return value or null if the query failed. 1762 * 1763 * @since 1.7.0 1764 * @throws RuntimeException 1765 */ 1766 public function loadRow() 1767 { 1768 $this->connect(); 1769 1770 $ret = null; 1771 1772 // Execute the query and get the result set cursor. 1773 if (!($cursor = $this->execute())) 1774 { 1775 return; 1776 } 1777 1778 // Get the first row from the result set as an array. 1779 if ($row = $this->fetchArray($cursor)) 1780 { 1781 $ret = $row; 1782 } 1783 1784 // Free up system resources and return. 1785 $this->freeResult($cursor); 1786 1787 return $ret; 1788 } 1789 1790 /** 1791 * Method to get an array of the result set rows from the database query where each row is an array. The array 1792 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array. 1793 * 1794 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted 1795 * behavior and should be avoided. 1796 * 1797 * @param integer $index The index of a field on which to key the result array. 1798 * 1799 * @return mixed The return value or null if the query failed. 1800 * 1801 * @since 1.7.0 1802 * @throws RuntimeException 1803 */ 1804 public function loadRowList($index = null) 1805 { 1806 $this->connect(); 1807 1808 $array = array(); 1809 1810 // Execute the query and get the result set cursor. 1811 if (!($cursor = $this->execute())) 1812 { 1813 return; 1814 } 1815 1816 // Get all of the rows from the result set as arrays. 1817 while ($row = $this->fetchArray($cursor)) 1818 { 1819 if ($index !== null) 1820 { 1821 $array[$row[$index]] = $row; 1822 } 1823 else 1824 { 1825 $array[] = $row; 1826 } 1827 } 1828 1829 // Free up system resources and return. 1830 $this->freeResult($cursor); 1831 1832 return $array; 1833 } 1834 1835 /** 1836 * Locks a table in the database. 1837 * 1838 * @param string $tableName The name of the table to unlock. 1839 * 1840 * @return JDatabaseDriver Returns this object to support chaining. 1841 * 1842 * @since 2.5.0 1843 * @throws RuntimeException 1844 */ 1845 abstract public function lockTable($tableName); 1846 1847 /** 1848 * Quotes and optionally escapes a string to database requirements for use in database queries. 1849 * 1850 * @param mixed $text A string or an array of strings to quote. 1851 * @param boolean $escape True (default) to escape the string, false to leave it unchanged. 1852 * 1853 * @return string|array The quoted input. 1854 * 1855 * @note Accepting an array of strings was added in 12.3. 1856 * @since 1.7.0 1857 */ 1858 public function quote($text, $escape = true) 1859 { 1860 if (is_array($text)) 1861 { 1862 foreach ($text as $k => $v) 1863 { 1864 $text[$k] = $this->quote($v, $escape); 1865 } 1866 1867 return $text; 1868 } 1869 else 1870 { 1871 return '\'' . ($escape ? $this->escape($text) : $text) . '\''; 1872 } 1873 } 1874 1875 /** 1876 * Quotes a binary string to database requirements for use in database queries. 1877 * 1878 * @param mixed $data A binary string to quote. 1879 * 1880 * @return string The binary quoted input string. 1881 * 1882 * @since 3.9.12 1883 */ 1884 public function quoteBinary($data) 1885 { 1886 // SQL standard syntax for hexadecimal literals 1887 return "X'" . bin2hex($data) . "'"; 1888 } 1889 1890 /** 1891 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection 1892 * risks and reserved word conflicts. 1893 * 1894 * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. 1895 * Each type supports dot-notation name. 1896 * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be 1897 * same length of $name; if is null there will not be any AS part for string or array element. 1898 * 1899 * @return mixed The quote wrapped name, same type of $name. 1900 * 1901 * @since 1.7.0 1902 */ 1903 public function quoteName($name, $as = null) 1904 { 1905 if (is_string($name)) 1906 { 1907 $quotedName = $this->quoteNameStr(explode('.', $name)); 1908 1909 $quotedAs = ''; 1910 1911 if (!is_null($as)) 1912 { 1913 settype($as, 'array'); 1914 $quotedAs .= ' AS ' . $this->quoteNameStr($as); 1915 } 1916 1917 return $quotedName . $quotedAs; 1918 } 1919 else 1920 { 1921 $fin = array(); 1922 1923 if (is_null($as)) 1924 { 1925 foreach ($name as $str) 1926 { 1927 $fin[] = $this->quoteName($str); 1928 } 1929 } 1930 elseif (is_array($name) && (count($name) == count($as))) 1931 { 1932 $count = count($name); 1933 1934 for ($i = 0; $i < $count; $i++) 1935 { 1936 $fin[] = $this->quoteName($name[$i], $as[$i]); 1937 } 1938 } 1939 1940 return $fin; 1941 } 1942 } 1943 1944 /** 1945 * Quote strings coming from quoteName call. 1946 * 1947 * @param array $strArr Array of strings coming from quoteName dot-explosion. 1948 * 1949 * @return string Dot-imploded string of quoted parts. 1950 * 1951 * @since 1.7.3 1952 */ 1953 protected function quoteNameStr($strArr) 1954 { 1955 $parts = array(); 1956 $q = $this->nameQuote; 1957 1958 foreach ($strArr as $part) 1959 { 1960 if (is_null($part)) 1961 { 1962 continue; 1963 } 1964 1965 if (strlen($q) == 1) 1966 { 1967 $parts[] = $q . str_replace($q, $q . $q, $part) . $q; 1968 } 1969 else 1970 { 1971 $parts[] = $q[0] . str_replace($q[1], $q[1] . $q[1], $part) . $q[1]; 1972 } 1973 } 1974 1975 return implode('.', $parts); 1976 } 1977 1978 /** 1979 * This function replaces a string identifier <var>$prefix</var> with the string held is the 1980 * <var>tablePrefix</var> class variable. 1981 * 1982 * @param string $sql The SQL statement to prepare. 1983 * @param string $prefix The common table prefix. 1984 * 1985 * @return string The processed SQL statement. 1986 * 1987 * @since 1.7.0 1988 */ 1989 public function replacePrefix($sql, $prefix = '#__') 1990 { 1991 $startPos = 0; 1992 $literal = ''; 1993 1994 $sql = trim($sql); 1995 $n = strlen($sql); 1996 1997 while ($startPos < $n) 1998 { 1999 $ip = strpos($sql, $prefix, $startPos); 2000 2001 if ($ip === false) 2002 { 2003 break; 2004 } 2005 2006 $j = strpos($sql, "'", $startPos); 2007 $k = strpos($sql, '"', $startPos); 2008 2009 if (($k !== false) && (($k < $j) || ($j === false))) 2010 { 2011 $quoteChar = '"'; 2012 $j = $k; 2013 } 2014 else 2015 { 2016 $quoteChar = "'"; 2017 } 2018 2019 if ($j === false) 2020 { 2021 $j = $n; 2022 } 2023 2024 $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos)); 2025 $startPos = $j; 2026 2027 $j = $startPos + 1; 2028 2029 if ($j >= $n) 2030 { 2031 break; 2032 } 2033 2034 // Quote comes first, find end of quote 2035 while (true) 2036 { 2037 $k = strpos($sql, $quoteChar, $j); 2038 $escaped = false; 2039 2040 if ($k === false) 2041 { 2042 break; 2043 } 2044 2045 $l = $k - 1; 2046 2047 while ($l >= 0 && $sql[$l] == '\\') 2048 { 2049 $l--; 2050 $escaped = !$escaped; 2051 } 2052 2053 if ($escaped) 2054 { 2055 $j = $k + 1; 2056 continue; 2057 } 2058 2059 break; 2060 } 2061 2062 if ($k === false) 2063 { 2064 // Error in the query - no end quote; ignore it 2065 break; 2066 } 2067 2068 $literal .= substr($sql, $startPos, $k - $startPos + 1); 2069 $startPos = $k + 1; 2070 } 2071 2072 if ($startPos < $n) 2073 { 2074 $literal .= substr($sql, $startPos, $n - $startPos); 2075 } 2076 2077 return $literal; 2078 } 2079 2080 /** 2081 * Renames a table in the database. 2082 * 2083 * @param string $oldTable The name of the table to be renamed 2084 * @param string $newTable The new name for the table. 2085 * @param string $backup Table prefix 2086 * @param string $prefix For the table - used to rename constraints in non-mysql databases 2087 * 2088 * @return JDatabaseDriver Returns this object to support chaining. 2089 * 2090 * @since 2.5.0 2091 * @throws RuntimeException 2092 */ 2093 abstract public function renameTable($oldTable, $newTable, $backup = null, $prefix = null); 2094 2095 /** 2096 * Select a database for use. 2097 * 2098 * @param string $database The name of the database to select for use. 2099 * 2100 * @return boolean True if the database was successfully selected. 2101 * 2102 * @since 1.7.0 2103 * @throws RuntimeException 2104 */ 2105 abstract public function select($database); 2106 2107 /** 2108 * Sets the database debugging state for the driver. 2109 * 2110 * @param boolean $level True to enable debugging. 2111 * 2112 * @return boolean The old debugging level. 2113 * 2114 * @since 1.7.0 2115 * @deprecated 4.0 This will be removed in Joomla 4 without replacement 2116 */ 2117 public function setDebug($level) 2118 { 2119 $previous = $this->debug; 2120 $this->debug = (bool) $level; 2121 2122 return $previous; 2123 } 2124 2125 /** 2126 * Sets the SQL statement string for later execution. 2127 * 2128 * @param mixed $query The SQL statement to set either as a JDatabaseQuery object or a string. 2129 * @param integer $offset The affected row offset to set. 2130 * @param integer $limit The maximum affected rows to set. 2131 * 2132 * @return JDatabaseDriver This object to support method chaining. 2133 * 2134 * @since 1.7.0 2135 */ 2136 public function setQuery($query, $offset = 0, $limit = 0) 2137 { 2138 $this->sql = $query; 2139 2140 if ($query instanceof JDatabaseQueryLimitable) 2141 { 2142 if (!$limit && $query->limit) 2143 { 2144 $limit = $query->limit; 2145 } 2146 2147 if (!$offset && $query->offset) 2148 { 2149 $offset = $query->offset; 2150 } 2151 2152 $query->setLimit($limit, $offset); 2153 } 2154 else 2155 { 2156 $this->limit = (int) max(0, $limit); 2157 $this->offset = (int) max(0, $offset); 2158 } 2159 2160 return $this; 2161 } 2162 2163 /** 2164 * Set the connection to use UTF-8 character encoding. 2165 * 2166 * @return boolean True on success. 2167 * 2168 * @since 1.7.0 2169 */ 2170 abstract public function setUtf(); 2171 2172 /** 2173 * Method to commit a transaction. 2174 * 2175 * @param boolean $toSavepoint If true, commit to the last savepoint. 2176 * 2177 * @return void 2178 * 2179 * @since 1.7.0 2180 * @throws RuntimeException 2181 */ 2182 abstract public function transactionCommit($toSavepoint = false); 2183 2184 /** 2185 * Method to roll back a transaction. 2186 * 2187 * @param boolean $toSavepoint If true, rollback to the last savepoint. 2188 * 2189 * @return void 2190 * 2191 * @since 1.7.0 2192 * @throws RuntimeException 2193 */ 2194 abstract public function transactionRollback($toSavepoint = false); 2195 2196 /** 2197 * Method to initialize a transaction. 2198 * 2199 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. 2200 * 2201 * @return void 2202 * 2203 * @since 1.7.0 2204 * @throws RuntimeException 2205 */ 2206 abstract public function transactionStart($asSavepoint = false); 2207 2208 /** 2209 * Method to truncate a table. 2210 * 2211 * @param string $table The table to truncate 2212 * 2213 * @return void 2214 * 2215 * @since 1.7.3 2216 * @throws RuntimeException 2217 */ 2218 public function truncateTable($table) 2219 { 2220 $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table)); 2221 $this->execute(); 2222 } 2223 2224 /** 2225 * Updates a row in a table based on an object's properties. 2226 * 2227 * @param string $table The name of the database table to update. 2228 * @param object &$object A reference to an object whose public properties match the table fields. 2229 * @param array|string $key The name of the primary key. 2230 * @param boolean $nulls True to update null fields or false to ignore them. 2231 * 2232 * @return boolean True on success. 2233 * 2234 * @since 1.7.0 2235 * @throws RuntimeException 2236 */ 2237 public function updateObject($table, &$object, $key, $nulls = false) 2238 { 2239 $fields = array(); 2240 $where = array(); 2241 2242 if (is_string($key)) 2243 { 2244 $key = array($key); 2245 } 2246 2247 if (is_object($key)) 2248 { 2249 $key = (array) $key; 2250 } 2251 2252 // Create the base update statement. 2253 $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s'; 2254 2255 // Iterate over the object variables to build the query fields/value pairs. 2256 foreach (get_object_vars($object) as $k => $v) 2257 { 2258 // Only process scalars that are not internal fields. 2259 if (is_array($v) || is_object($v) || $k[0] === '_') 2260 { 2261 continue; 2262 } 2263 2264 // Set the primary key to the WHERE clause instead of a field to update. 2265 if (in_array($k, $key)) 2266 { 2267 $where[] = $this->quoteName($k) . ($v === null ? ' IS NULL' : ' = ' . $this->quote($v)); 2268 continue; 2269 } 2270 2271 // Prepare and sanitize the fields and values for the database query. 2272 if ($v === null) 2273 { 2274 // If the value is null and we want to update nulls then set it. 2275 if ($nulls) 2276 { 2277 $val = 'NULL'; 2278 } 2279 // If the value is null and we do not want to update nulls then ignore this field. 2280 else 2281 { 2282 continue; 2283 } 2284 } 2285 // The field is not null so we prep it for update. 2286 else 2287 { 2288 $val = $this->quote($v); 2289 } 2290 2291 // Add the field to be updated. 2292 $fields[] = $this->quoteName($k) . '=' . $val; 2293 } 2294 2295 // We don't have any fields to update. 2296 if (empty($fields)) 2297 { 2298 return true; 2299 } 2300 2301 // Set the query and execute the update. 2302 $this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where))); 2303 2304 return $this->execute(); 2305 } 2306 2307 /** 2308 * Execute the SQL statement. 2309 * 2310 * @return mixed A database cursor resource on success, boolean false on failure. 2311 * 2312 * @since 3.0.0 2313 * @throws RuntimeException 2314 */ 2315 abstract public function execute(); 2316 2317 /** 2318 * Unlocks tables in the database. 2319 * 2320 * @return JDatabaseDriver Returns this object to support chaining. 2321 * 2322 * @since 2.5.0 2323 * @throws RuntimeException 2324 */ 2325 abstract public function unlockTables(); 2326} 2327