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