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
13 defined('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 */
21 abstract 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