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