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