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 * 8 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects 9 * instead of plain stdClass objects 10 */ 11 12// Protect from unauthorized access 13defined('FOF_INCLUDED') or die; 14 15/** 16 * Joomla Platform PDO Database Driver Class 17 * 18 * @see http://php.net/pdo 19 * @since 12.1 20 */ 21abstract class FOFDatabaseDriverPdo extends FOFDatabaseDriver 22{ 23 /** 24 * The name of the database driver. 25 * 26 * @var string 27 * @since 12.1 28 */ 29 public $name = 'pdo'; 30 31 /** 32 * @var PDO The database connection resource. 33 * @since 12.1 34 */ 35 protected $connection; 36 37 /** 38 * The character(s) used to quote SQL statement names such as table names or field names, 39 * etc. The child classes should define this as necessary. If a single character string the 40 * same character is used for both sides of the quoted name, else the first character will be 41 * used for the opening quote and the second for the closing quote. 42 * 43 * @var string 44 * @since 12.1 45 */ 46 protected $nameQuote = "'"; 47 48 /** 49 * The null or zero representation of a timestamp for the database driver. This should be 50 * defined in child classes to hold the appropriate value for the engine. 51 * 52 * @var string 53 * @since 12.1 54 */ 55 protected $nullDate = '0000-00-00 00:00:00'; 56 57 /** 58 * @var resource The prepared statement. 59 * @since 12.1 60 */ 61 protected $prepared; 62 63 /** 64 * Contains the current query execution status 65 * 66 * @var array 67 * @since 12.1 68 */ 69 protected $executed = false; 70 71 /** 72 * Constructor. 73 * 74 * @param array $options List of options used to configure the connection 75 * 76 * @since 12.1 77 */ 78 public function __construct($options) 79 { 80 // Get some basic values from the options. 81 $options['driver'] = (isset($options['driver'])) ? $options['driver'] : 'odbc'; 82 $options['dsn'] = (isset($options['dsn'])) ? $options['dsn'] : ''; 83 $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost'; 84 $options['database'] = (isset($options['database'])) ? $options['database'] : ''; 85 $options['user'] = (isset($options['user'])) ? $options['user'] : ''; 86 $options['password'] = (isset($options['password'])) ? $options['password'] : ''; 87 $options['driverOptions'] = (isset($options['driverOptions'])) ? $options['driverOptions'] : array(); 88 89 $hostParts = explode(':', $options['host']); 90 91 if (!empty($hostParts[1])) 92 { 93 $options['host'] = $hostParts[0]; 94 $options['port'] = $hostParts[1]; 95 } 96 97 // Finalize initialisation 98 parent::__construct($options); 99 } 100 101 /** 102 * Destructor. 103 * 104 * @since 12.1 105 */ 106 public function __destruct() 107 { 108 $this->disconnect(); 109 } 110 111 /** 112 * Connects to the database if needed. 113 * 114 * @return void Returns void if the database connected successfully. 115 * 116 * @since 12.1 117 * @throws RuntimeException 118 */ 119 public function connect() 120 { 121 if ($this->connection) 122 { 123 return; 124 } 125 126 // Make sure the PDO extension for PHP is installed and enabled. 127 if (!self::isSupported()) 128 { 129 throw new RuntimeException('PDO Extension is not available.', 1); 130 } 131 132 $replace = array(); 133 $with = array(); 134 135 // Find the correct PDO DSN Format to use: 136 switch ($this->options['driver']) 137 { 138 case 'cubrid': 139 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 33000; 140 141 $format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 142 143 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 144 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 145 146 break; 147 148 case 'dblib': 149 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433; 150 151 $format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 152 153 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 154 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 155 156 break; 157 158 case 'firebird': 159 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3050; 160 161 $format = 'firebird:dbname=#DBNAME#'; 162 163 $replace = array('#DBNAME#'); 164 $with = array($this->options['database']); 165 166 break; 167 168 case 'ibm': 169 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 56789; 170 171 if (!empty($this->options['dsn'])) 172 { 173 $format = 'ibm:DSN=#DSN#'; 174 175 $replace = array('#DSN#'); 176 $with = array($this->options['dsn']); 177 } 178 else 179 { 180 $format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#'; 181 182 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 183 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 184 } 185 186 break; 187 188 case 'informix': 189 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1526; 190 $this->options['protocol'] = (isset($this->options['protocol'])) ? $this->options['protocol'] : 'onsoctcp'; 191 192 if (!empty($this->options['dsn'])) 193 { 194 $format = 'informix:DSN=#DSN#'; 195 196 $replace = array('#DSN#'); 197 $with = array($this->options['dsn']); 198 } 199 else 200 { 201 $format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#'; 202 203 $replace = array('#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#'); 204 $with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['server'], $this->options['protocol']); 205 } 206 207 break; 208 209 case 'mssql': 210 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433; 211 212 $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 213 214 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 215 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 216 217 break; 218 219 // The pdomysql case is a special case within the CMS environment 220 case 'pdomysql': 221 case 'mysql': 222 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3306; 223 224 $format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#'; 225 226 $replace = array('#HOST#', '#PORT#', '#DBNAME#', '#CHARSET#'); 227 $with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['charset']); 228 229 break; 230 231 case 'oci': 232 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1521; 233 $this->options['charset'] = (isset($this->options['charset'])) ? $this->options['charset'] : 'AL32UTF8'; 234 235 if (!empty($this->options['dsn'])) 236 { 237 $format = 'oci:dbname=#DSN#'; 238 239 $replace = array('#DSN#'); 240 $with = array($this->options['dsn']); 241 } 242 else 243 { 244 $format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#'; 245 246 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 247 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 248 } 249 250 $format .= ';charset=' . $this->options['charset']; 251 252 break; 253 254 case 'odbc': 255 $format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#'; 256 257 $replace = array('#DSN#', '#USER#', '#PASSWORD#'); 258 $with = array($this->options['dsn'], $this->options['user'], $this->options['password']); 259 260 break; 261 262 case 'pgsql': 263 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 5432; 264 265 $format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 266 267 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 268 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 269 270 break; 271 272 case 'sqlite': 273 if (isset($this->options['version']) && $this->options['version'] == 2) 274 { 275 $format = 'sqlite2:#DBNAME#'; 276 } 277 else 278 { 279 $format = 'sqlite:#DBNAME#'; 280 } 281 282 $replace = array('#DBNAME#'); 283 $with = array($this->options['database']); 284 285 break; 286 287 case 'sybase': 288 $this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433; 289 290 $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 291 292 $replace = array('#HOST#', '#PORT#', '#DBNAME#'); 293 $with = array($this->options['host'], $this->options['port'], $this->options['database']); 294 295 break; 296 } 297 298 // Create the connection string: 299 $connectionString = str_replace($replace, $with, $format); 300 301 try 302 { 303 $this->connection = new PDO( 304 $connectionString, 305 $this->options['user'], 306 $this->options['password'], 307 $this->options['driverOptions'] 308 ); 309 } 310 catch (PDOException $e) 311 { 312 throw new RuntimeException('Could not connect to PDO: ' . $e->getMessage(), 2, $e); 313 } 314 } 315 316 /** 317 * Disconnects the database. 318 * 319 * @return void 320 * 321 * @since 12.1 322 */ 323 public function disconnect() 324 { 325 foreach ($this->disconnectHandlers as $h) 326 { 327 call_user_func_array($h, array( &$this)); 328 } 329 330 $this->freeResult(); 331 unset($this->connection); 332 } 333 334 /** 335 * Method to escape a string for usage in an SQL statement. 336 * 337 * Oracle escaping reference: 338 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F 339 * 340 * SQLite escaping notes: 341 * http://www.sqlite.org/faq.html#q14 342 * 343 * Method body is as implemented by the Zend Framework 344 * 345 * Note: Using query objects with bound variables is 346 * preferable to the below. 347 * 348 * @param string $text The string to be escaped. 349 * @param boolean $extra Unused optional parameter to provide extra escaping. 350 * 351 * @return string The escaped string. 352 * 353 * @since 12.1 354 */ 355 public function escape($text, $extra = false) 356 { 357 if (is_int($text) || is_float($text)) 358 { 359 return $text; 360 } 361 362 $text = str_replace("'", "''", $text); 363 364 return addcslashes($text, "\000\n\r\\\032"); 365 } 366 367 /** 368 * Execute the SQL statement. 369 * 370 * @return mixed A database cursor resource on success, boolean false on failure. 371 * 372 * @since 12.1 373 * @throws RuntimeException 374 * @throws Exception 375 */ 376 public function execute() 377 { 378 $this->connect(); 379 380 if (!is_object($this->connection)) 381 { 382 if (class_exists('JLog')) 383 { 384 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database'); 385 } 386 throw new RuntimeException($this->errorMsg, $this->errorNum); 387 } 388 389 // Take a local copy so that we don't modify the original query and cause issues later 390 $query = $this->replacePrefix((string) $this->sql); 391 392 if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0)) 393 { 394 // @TODO 395 $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit; 396 } 397 398 // Increment the query counter. 399 $this->count++; 400 401 // Reset the error values. 402 $this->errorNum = 0; 403 $this->errorMsg = ''; 404 405 // If debugging is enabled then let's log the query. 406 if ($this->debug) 407 { 408 // Add the query to the object queue. 409 $this->log[] = $query; 410 411 if (class_exists('JLog')) 412 { 413 JLog::add($query, JLog::DEBUG, 'databasequery'); 414 } 415 416 $this->timings[] = microtime(true); 417 } 418 419 // Execute the query. 420 $this->executed = false; 421 422 if ($this->prepared instanceof PDOStatement) 423 { 424 // Bind the variables: 425 if ($this->sql instanceof FOFDatabaseQueryPreparable) 426 { 427 $bounded = $this->sql->getBounded(); 428 429 foreach ($bounded as $key => $obj) 430 { 431 $this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions); 432 } 433 } 434 435 $this->executed = $this->prepared->execute(); 436 } 437 438 if ($this->debug) 439 { 440 $this->timings[] = microtime(true); 441 442 if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) 443 { 444 $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 445 } 446 else 447 { 448 $this->callStacks[] = debug_backtrace(); 449 } 450 } 451 452 // If an error occurred handle it. 453 if (!$this->executed) 454 { 455 // Get the error number and message before we execute any more queries. 456 $errorNum = $this->getErrorNumber(); 457 $errorMsg = $this->getErrorMessage($query); 458 459 // Check if the server was disconnected. 460 if (!$this->connected()) 461 { 462 try 463 { 464 // Attempt to reconnect. 465 $this->connection = null; 466 $this->connect(); 467 } 468 // If connect fails, ignore that exception and throw the normal exception. 469 catch (RuntimeException $e) 470 { 471 // Get the error number and message. 472 $this->errorNum = $this->getErrorNumber(); 473 $this->errorMsg = $this->getErrorMessage($query); 474 475 // Throw the normal query exception. 476 if (class_exists('JLog')) 477 { 478 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); 479 } 480 481 throw new RuntimeException($this->errorMsg, $this->errorNum, $e); 482 } 483 484 // Since we were able to reconnect, run the query again. 485 return $this->execute(); 486 } 487 // The server was not disconnected. 488 else 489 { 490 // Get the error number and message from before we tried to reconnect. 491 $this->errorNum = $errorNum; 492 $this->errorMsg = $errorMsg; 493 494 // Throw the normal query exception. 495 if (class_exists('JLog')) 496 { 497 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); 498 } 499 500 throw new RuntimeException($this->errorMsg, $this->errorNum); 501 } 502 } 503 504 return $this->prepared; 505 } 506 507 /** 508 * Retrieve a PDO database connection attribute 509 * http://www.php.net/manual/en/pdo.getattribute.php 510 * 511 * Usage: $db->getOption(PDO::ATTR_CASE); 512 * 513 * @param mixed $key One of the PDO::ATTR_* Constants 514 * 515 * @return mixed 516 * 517 * @since 12.1 518 */ 519 public function getOption($key) 520 { 521 $this->connect(); 522 523 return $this->connection->getAttribute($key); 524 } 525 526 /** 527 * Get a query to run and verify the database is operational. 528 * 529 * @return string The query to check the health of the DB. 530 * 531 * @since 12.2 532 */ 533 public function getConnectedQuery() 534 { 535 return 'SELECT 1'; 536 } 537 538 /** 539 * Sets an attribute on the PDO database handle. 540 * http://www.php.net/manual/en/pdo.setattribute.php 541 * 542 * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); 543 * 544 * @param integer $key One of the PDO::ATTR_* Constants 545 * @param mixed $value One of the associated PDO Constants 546 * related to the particular attribute 547 * key. 548 * 549 * @return boolean 550 * 551 * @since 12.1 552 */ 553 public function setOption($key, $value) 554 { 555 $this->connect(); 556 557 return $this->connection->setAttribute($key, $value); 558 } 559 560 /** 561 * Test to see if the PDO extension is available. 562 * Override as needed to check for specific PDO Drivers. 563 * 564 * @return boolean True on success, false otherwise. 565 * 566 * @since 12.1 567 */ 568 public static function isSupported() 569 { 570 return defined('PDO::ATTR_DRIVER_NAME'); 571 } 572 573 /** 574 * Determines if the connection to the server is active. 575 * 576 * @return boolean True if connected to the database engine. 577 * 578 * @since 12.1 579 */ 580 public function connected() 581 { 582 // Flag to prevent recursion into this function. 583 static $checkingConnected = false; 584 585 if ($checkingConnected) 586 { 587 // Reset this flag and throw an exception. 588 $checkingConnected = true; 589 die('Recursion trying to check if connected.'); 590 } 591 592 // Backup the query state. 593 $query = $this->sql; 594 $limit = $this->limit; 595 $offset = $this->offset; 596 $prepared = $this->prepared; 597 598 try 599 { 600 // Set the checking connection flag. 601 $checkingConnected = true; 602 603 // Run a simple query to check the connection. 604 $this->setQuery($this->getConnectedQuery()); 605 $status = (bool) $this->loadResult(); 606 } 607 // If we catch an exception here, we must not be connected. 608 catch (Exception $e) 609 { 610 $status = false; 611 } 612 613 // Restore the query state. 614 $this->sql = $query; 615 $this->limit = $limit; 616 $this->offset = $offset; 617 $this->prepared = $prepared; 618 $checkingConnected = false; 619 620 return $status; 621 } 622 623 /** 624 * Get the number of affected rows for the previous executed SQL statement. 625 * Only applicable for DELETE, INSERT, or UPDATE statements. 626 * 627 * @return integer The number of affected rows. 628 * 629 * @since 12.1 630 */ 631 public function getAffectedRows() 632 { 633 $this->connect(); 634 635 if ($this->prepared instanceof PDOStatement) 636 { 637 return $this->prepared->rowCount(); 638 } 639 else 640 { 641 return 0; 642 } 643 } 644 645 /** 646 * Get the number of returned rows for the previous executed SQL statement. 647 * Only applicable for DELETE, INSERT, or UPDATE statements. 648 * 649 * @param resource $cursor An optional database cursor resource to extract the row count from. 650 * 651 * @return integer The number of returned rows. 652 * 653 * @since 12.1 654 */ 655 public function getNumRows($cursor = null) 656 { 657 $this->connect(); 658 659 if ($cursor instanceof PDOStatement) 660 { 661 return $cursor->rowCount(); 662 } 663 elseif ($this->prepared instanceof PDOStatement) 664 { 665 return $this->prepared->rowCount(); 666 } 667 else 668 { 669 return 0; 670 } 671 } 672 673 /** 674 * Method to get the auto-incremented value from the last INSERT statement. 675 * 676 * @return string The value of the auto-increment field from the last inserted row. 677 * 678 * @since 12.1 679 */ 680 public function insertid() 681 { 682 $this->connect(); 683 684 // Error suppress this to prevent PDO warning us that the driver doesn't support this operation. 685 return @$this->connection->lastInsertId(); 686 } 687 688 /** 689 * Select a database for use. 690 * 691 * @param string $database The name of the database to select for use. 692 * 693 * @return boolean True if the database was successfully selected. 694 * 695 * @since 12.1 696 * @throws RuntimeException 697 */ 698 public function select($database) 699 { 700 $this->connect(); 701 702 return true; 703 } 704 705 /** 706 * Sets the SQL statement string for later execution. 707 * 708 * @param mixed $query The SQL statement to set either as a FOFDatabaseQuery object or a string. 709 * @param integer $offset The affected row offset to set. 710 * @param integer $limit The maximum affected rows to set. 711 * @param array $driverOptions The optional PDO driver options. 712 * 713 * @return FOFDatabaseDriver This object to support method chaining. 714 * 715 * @since 12.1 716 */ 717 public function setQuery($query, $offset = null, $limit = null, $driverOptions = array()) 718 { 719 $this->connect(); 720 721 $this->freeResult(); 722 723 if (is_string($query)) 724 { 725 // Allows taking advantage of bound variables in a direct query: 726 $query = $this->getQuery(true)->setQuery($query); 727 } 728 729 if ($query instanceof FOFDatabaseQueryLimitable && !is_null($offset) && !is_null($limit)) 730 { 731 $query = $query->processLimit($query, $limit, $offset); 732 } 733 734 // Create a stringified version of the query (with prefixes replaced): 735 $sql = $this->replacePrefix((string) $query); 736 737 // Use the stringified version in the prepare call: 738 $this->prepared = $this->connection->prepare($sql, $driverOptions); 739 740 // Store reference to the original FOFDatabaseQuery instance within the class. 741 // This is important since binding variables depends on it within execute(): 742 parent::setQuery($query, $offset, $limit); 743 744 return $this; 745 } 746 747 /** 748 * Set the connection to use UTF-8 character encoding. 749 * 750 * @return boolean True on success. 751 * 752 * @since 12.1 753 */ 754 public function setUtf() 755 { 756 return false; 757 } 758 759 /** 760 * Method to commit a transaction. 761 * 762 * @param boolean $toSavepoint If true, commit to the last savepoint. 763 * 764 * @return void 765 * 766 * @since 12.1 767 * @throws RuntimeException 768 */ 769 public function transactionCommit($toSavepoint = false) 770 { 771 $this->connect(); 772 773 if (!$toSavepoint || $this->transactionDepth == 1) 774 { 775 $this->connection->commit(); 776 } 777 778 $this->transactionDepth--; 779 } 780 781 /** 782 * Method to roll back a transaction. 783 * 784 * @param boolean $toSavepoint If true, rollback to the last savepoint. 785 * 786 * @return void 787 * 788 * @since 12.1 789 * @throws RuntimeException 790 */ 791 public function transactionRollback($toSavepoint = false) 792 { 793 $this->connect(); 794 795 if (!$toSavepoint || $this->transactionDepth == 1) 796 { 797 $this->connection->rollBack(); 798 } 799 800 $this->transactionDepth--; 801 } 802 803 /** 804 * Method to initialize a transaction. 805 * 806 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. 807 * 808 * @return void 809 * 810 * @since 12.1 811 * @throws RuntimeException 812 */ 813 public function transactionStart($asSavepoint = false) 814 { 815 $this->connect(); 816 817 if (!$asSavepoint || !$this->transactionDepth) 818 { 819 $this->connection->beginTransaction(); 820 } 821 822 $this->transactionDepth++; 823 } 824 825 /** 826 * Method to fetch a row from the result set cursor as an array. 827 * 828 * @param mixed $cursor The optional result set cursor from which to fetch the row. 829 * 830 * @return mixed Either the next row from the result set or false if there are no more rows. 831 * 832 * @since 12.1 833 */ 834 protected function fetchArray($cursor = null) 835 { 836 if (!empty($cursor) && $cursor instanceof PDOStatement) 837 { 838 return $cursor->fetch(PDO::FETCH_NUM); 839 } 840 841 if ($this->prepared instanceof PDOStatement) 842 { 843 return $this->prepared->fetch(PDO::FETCH_NUM); 844 } 845 } 846 847 /** 848 * Method to fetch a row from the result set cursor as an associative array. 849 * 850 * @param mixed $cursor The optional result set cursor from which to fetch the row. 851 * 852 * @return mixed Either the next row from the result set or false if there are no more rows. 853 * 854 * @since 12.1 855 */ 856 protected function fetchAssoc($cursor = null) 857 { 858 if (!empty($cursor) && $cursor instanceof PDOStatement) 859 { 860 return $cursor->fetch(PDO::FETCH_ASSOC); 861 } 862 863 if ($this->prepared instanceof PDOStatement) 864 { 865 return $this->prepared->fetch(PDO::FETCH_ASSOC); 866 } 867 } 868 869 /** 870 * Method to fetch a row from the result set cursor as an object. 871 * 872 * @param mixed $cursor The optional result set cursor from which to fetch the row. 873 * @param string $class Unused, only necessary so method signature will be the same as parent. 874 * 875 * @return mixed Either the next row from the result set or false if there are no more rows. 876 * 877 * @since 12.1 878 */ 879 protected function fetchObject($cursor = null, $class = 'stdClass') 880 { 881 if (!empty($cursor) && $cursor instanceof PDOStatement) 882 { 883 return $cursor->fetchObject($class); 884 } 885 886 if ($this->prepared instanceof PDOStatement) 887 { 888 return $this->prepared->fetchObject($class); 889 } 890 } 891 892 /** 893 * Method to free up the memory used for the result set. 894 * 895 * @param mixed $cursor The optional result set cursor from which to fetch the row. 896 * 897 * @return void 898 * 899 * @since 12.1 900 */ 901 protected function freeResult($cursor = null) 902 { 903 $this->executed = false; 904 905 if ($cursor instanceof PDOStatement) 906 { 907 $cursor->closeCursor(); 908 $cursor = null; 909 } 910 911 if ($this->prepared instanceof PDOStatement) 912 { 913 $this->prepared->closeCursor(); 914 $this->prepared = null; 915 } 916 } 917 918 /** 919 * Method to get the next row in the result set from the database query as an object. 920 * 921 * @param string $class The class name to use for the returned row object. 922 * 923 * @return mixed The result of the query as an array, false if there are no more rows. 924 * 925 * @since 12.1 926 * @throws RuntimeException 927 * @deprecated 4.0 (CMS) Use getIterator() instead 928 */ 929 public function loadNextObject($class = 'stdClass') 930 { 931 if (class_exists('JLog')) 932 { 933 JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated'); 934 } 935 $this->connect(); 936 937 // Execute the query and get the result set cursor. 938 if (!$this->executed) 939 { 940 if (!($this->execute())) 941 { 942 return $this->errorNum ? null : false; 943 } 944 } 945 946 // Get the next row from the result set as an object of type $class. 947 if ($row = $this->fetchObject(null, $class)) 948 { 949 return $row; 950 } 951 952 // Free up system resources and return. 953 $this->freeResult(); 954 955 return false; 956 } 957 958 /** 959 * Method to get the next row in the result set from the database query as an array. 960 * 961 * @return mixed The result of the query as an array, false if there are no more rows. 962 * 963 * @since 12.1 964 * @throws RuntimeException 965 */ 966 public function loadNextAssoc() 967 { 968 $this->connect(); 969 970 // Execute the query and get the result set cursor. 971 if (!$this->executed) 972 { 973 if (!($this->execute())) 974 { 975 return $this->errorNum ? null : false; 976 } 977 } 978 979 // Get the next row from the result set as an object of type $class. 980 if ($row = $this->fetchAssoc()) 981 { 982 return $row; 983 } 984 985 // Free up system resources and return. 986 $this->freeResult(); 987 988 return false; 989 } 990 991 /** 992 * Method to get the next row in the result set from the database query as an array. 993 * 994 * @return mixed The result of the query as an array, false if there are no more rows. 995 * 996 * @since 12.1 997 * @throws RuntimeException 998 * @deprecated 4.0 (CMS) Use getIterator() instead 999 */ 1000 public function loadNextRow() 1001 { 1002 if (class_exists('JLog')) 1003 { 1004 JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated'); 1005 } 1006 $this->connect(); 1007 1008 // Execute the query and get the result set cursor. 1009 if (!$this->executed) 1010 { 1011 if (!($this->execute())) 1012 { 1013 return $this->errorNum ? null : false; 1014 } 1015 } 1016 1017 // Get the next row from the result set as an object of type $class. 1018 if ($row = $this->fetchArray()) 1019 { 1020 return $row; 1021 } 1022 1023 // Free up system resources and return. 1024 $this->freeResult(); 1025 1026 return false; 1027 } 1028 1029 /** 1030 * PDO does not support serialize 1031 * 1032 * @return array 1033 * 1034 * @since 12.3 1035 */ 1036 public function __sleep() 1037 { 1038 $serializedProperties = array(); 1039 1040 $reflect = new ReflectionClass($this); 1041 1042 // Get properties of the current class 1043 $properties = $reflect->getProperties(); 1044 1045 foreach ($properties as $property) 1046 { 1047 // Do not serialize properties that are PDO 1048 if ($property->isStatic() == false && !($this->{$property->name} instanceof PDO)) 1049 { 1050 array_push($serializedProperties, $property->name); 1051 } 1052 } 1053 1054 return $serializedProperties; 1055 } 1056 1057 /** 1058 * Wake up after serialization 1059 * 1060 * @return array 1061 * 1062 * @since 12.3 1063 */ 1064 public function __wakeup() 1065 { 1066 // Get connection back 1067 $this->__construct($this->options); 1068 } 1069 1070 /** 1071 * Return the actual SQL Error number 1072 * 1073 * @return integer The SQL Error number 1074 * 1075 * @since 3.4.6 1076 */ 1077 protected function getErrorNumber() 1078 { 1079 return (int) $this->connection->errorCode(); 1080 } 1081 1082 /** 1083 * Return the actual SQL Error message 1084 * 1085 * @param string $query The SQL Query that fails 1086 * 1087 * @return string The SQL Error message 1088 * 1089 * @since 3.4.6 1090 */ 1091 protected function getErrorMessage($query) 1092 { 1093 // Note we ignoring $query here as it not used in the original code. 1094 1095 // The SQL Error Information 1096 $errorInfo = implode(", ", $this->connection->errorInfo()); 1097 1098 // Replace the Databaseprefix with `#__` if we are not in Debug 1099 if (!$this->debug) 1100 { 1101 $errorInfo = str_replace($this->tablePrefix, '#__', $errorInfo); 1102 } 1103 1104 return 'SQL: ' . $errorInfo; 1105 } 1106} 1107