1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Database
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * MySQLi database driver
14 *
15 * @link https://secure.php.net/manual/en/book.mysqli.php
16 * @since 12.1
17 */
18 class JDatabaseDriverMysqli extends JDatabaseDriver
19 {
20 /**
21 * The name of the database driver.
22 *
23 * @var string
24 * @since 12.1
25 */
26 public $name = 'mysqli';
27
28 /**
29 * The type of the database server family supported by this driver.
30 *
31 * @var string
32 * @since CMS 3.5.0
33 */
34 public $serverType = 'mysql';
35
36 /**
37 * @var mysqli The database connection resource.
38 * @since 11.1
39 */
40 protected $connection;
41
42 /**
43 * The character(s) used to quote SQL statement names such as table names or field names,
44 * etc. The child classes should define this as necessary. If a single character string the
45 * same character is used for both sides of the quoted name, else the first character will be
46 * used for the opening quote and the second for the closing quote.
47 *
48 * @var string
49 * @since 12.2
50 */
51 protected $nameQuote = '`';
52
53 /**
54 * The null or zero representation of a timestamp for the database driver. This should be
55 * defined in child classes to hold the appropriate value for the engine.
56 *
57 * @var string
58 * @since 12.2
59 */
60 protected $nullDate = '0000-00-00 00:00:00';
61
62 /**
63 * @var string The minimum supported database version.
64 * @since 12.2
65 */
66 protected static $dbMinimum = '5.0.4';
67
68 /**
69 * Constructor.
70 *
71 * @param array $options List of options used to configure the connection
72 *
73 * @since 12.1
74 */
75 public function __construct($options)
76 {
77 // Get some basic values from the options.
78 $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
79 $options['user'] = (isset($options['user'])) ? $options['user'] : '';
80 $options['password'] = (isset($options['password'])) ? $options['password'] : '';
81 $options['database'] = (isset($options['database'])) ? $options['database'] : '';
82 $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;
83 $options['port'] = (isset($options['port'])) ? (int) $options['port'] : null;
84 $options['socket'] = (isset($options['socket'])) ? $options['socket'] : null;
85
86 // Finalize initialisation.
87 parent::__construct($options);
88 }
89
90 /**
91 * Destructor.
92 *
93 * @since 12.1
94 */
95 public function __destruct()
96 {
97 $this->disconnect();
98 }
99
100 /**
101 * Connects to the database if needed.
102 *
103 * @return void Returns void if the database connected successfully.
104 *
105 * @since 12.1
106 * @throws RuntimeException
107 */
108 public function connect()
109 {
110 if ($this->connection)
111 {
112 return;
113 }
114
115 /*
116 * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we
117 * have to extract them from the host string.
118 */
119 $port = isset($this->options['port']) ? $this->options['port'] : 3306;
120 $regex = '/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/';
121
122 if (preg_match($regex, $this->options['host'], $matches))
123 {
124 // It's an IPv4 address with or without port
125 $this->options['host'] = $matches['host'];
126
127 if (!empty($matches['port']))
128 {
129 $port = $matches['port'];
130 }
131 }
132 elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $this->options['host'], $matches))
133 {
134 // We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306
135 $this->options['host'] = $matches['host'];
136
137 if (!empty($matches['port']))
138 {
139 $port = $matches['port'];
140 }
141 }
142 elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $this->options['host'], $matches))
143 {
144 // Named host (e.g example.com or localhost) with or without port
145 $this->options['host'] = $matches['host'];
146
147 if (!empty($matches['port']))
148 {
149 $port = $matches['port'];
150 }
151 }
152 elseif (preg_match('/^:(?P<port>[^:]+)$/', $this->options['host'], $matches))
153 {
154 // Empty host, just port, e.g. ':3306'
155 $this->options['host'] = 'localhost';
156 $port = $matches['port'];
157 }
158 // ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default
159
160 // Get the port number or socket name
161 if (is_numeric($port))
162 {
163 $this->options['port'] = (int) $port;
164 }
165 else
166 {
167 $this->options['socket'] = $port;
168 }
169
170 // Make sure the MySQLi extension for PHP is installed and enabled.
171 if (!self::isSupported())
172 {
173 throw new JDatabaseExceptionUnsupported('The MySQL adapter mysqli is not available');
174 }
175
176 $this->connection = @mysqli_connect(
177 $this->options['host'], $this->options['user'], $this->options['password'], null, $this->options['port'], $this->options['socket']
178 );
179
180 // Attempt to connect to the server.
181 if (!$this->connection)
182 {
183 throw new JDatabaseExceptionConnecting('Could not connect to MySQL.');
184 }
185
186 $this->options['sqlModes'] = [
187 'ONLY_FULL_GROUP_BY',
188 'STRICT_TRANS_TABLES',
189 'ERROR_FOR_DIVISION_BY_ZERO',
190 'NO_AUTO_CREATE_USER',
191 'NO_ENGINE_SUBSTITUTION',
192 ];
193 if ($this->options['sqlModes'] !== [])
194 {
195 $this->connection->query('SET @@SESSION.sql_mode = \'' . implode(',', $this->options['sqlModes']) . '\';');
196 }
197
198 // Set sql_mode to non_strict mode
199 mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';");
200
201 // If auto-select is enabled select the given database.
202 if ($this->options['select'] && !empty($this->options['database']))
203 {
204 $this->select($this->options['database']);
205 }
206
207 // Pre-populate the UTF-8 Multibyte compatibility flag based on server version
208 $this->utf8mb4 = $this->serverClaimsUtf8mb4Support();
209
210 // Set the character set (needed for MySQL 4.1.2+).
211 $this->utf = $this->setUtf();
212
213 // Turn MySQL profiling ON in debug mode:
214 if ($this->debug && $this->hasProfiling())
215 {
216 mysqli_query($this->connection, 'SET profiling_history_size = 100;');
217 mysqli_query($this->connection, 'SET profiling = 1;');
218 }
219 }
220
221 /**
222 * Disconnects the database.
223 *
224 * @return void
225 *
226 * @since 12.1
227 */
228 public function disconnect()
229 {
230 // Close the connection.
231 if ($this->connection instanceof mysqli && $this->connection->stat() !== false)
232 {
233 foreach ($this->disconnectHandlers as $h)
234 {
235 call_user_func_array($h, array( &$this));
236 }
237
238 mysqli_close($this->connection);
239 }
240
241 $this->connection = null;
242 }
243
244 /**
245 * Method to escape a string for usage in an SQL statement.
246 *
247 * @param string $text The string to be escaped.
248 * @param boolean $extra Optional parameter to provide extra escaping.
249 *
250 * @return string The escaped string.
251 *
252 * @since 12.1
253 */
254 public function escape($text, $extra = false)
255 {
256 $this->connect();
257
258 $result = mysqli_real_escape_string($this->getConnection(), $text);
259
260 if ($extra)
261 {
262 $result = addcslashes($result, '%_');
263 }
264
265 return $result;
266 }
267
268 /**
269 * Test to see if the MySQL connector is available.
270 *
271 * @return boolean True on success, false otherwise.
272 *
273 * @since 12.1
274 */
275 public static function isSupported()
276 {
277 return function_exists('mysqli_connect');
278 }
279
280 /**
281 * Determines if the connection to the server is active.
282 *
283 * @return boolean True if connected to the database engine.
284 *
285 * @since 12.1
286 */
287 public function connected()
288 {
289 if (is_object($this->connection))
290 {
291 return mysqli_ping($this->connection);
292 }
293
294 return false;
295 }
296
297 /**
298 * Drops a table from the database.
299 *
300 * @param string $tableName The name of the database table to drop.
301 * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
302 *
303 * @return JDatabaseDriverMysqli Returns this object to support chaining.
304 *
305 * @since 12.2
306 * @throws RuntimeException
307 */
308 public function dropTable($tableName, $ifExists = true)
309 {
310 $this->connect();
311
312 $query = $this->getQuery(true);
313
314 $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));
315
316 $this->execute();
317
318 return $this;
319 }
320
321 /**
322 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
323 *
324 * @return integer The number of affected rows.
325 *
326 * @since 12.1
327 */
328 public function getAffectedRows()
329 {
330 $this->connect();
331
332 return mysqli_affected_rows($this->connection);
333 }
334
335 /**
336 * Method to get the database collation.
337 *
338 * @return mixed The collation in use by the database (string) or boolean false if not supported.
339 *
340 * @since 12.2
341 * @throws RuntimeException
342 */
343 public function getCollation()
344 {
345 $this->connect();
346
347 // Attempt to get the database collation by accessing the server system variable.
348 $this->setQuery('SHOW VARIABLES LIKE "collation_database"');
349 $result = $this->loadObject();
350
351 if (property_exists($result, 'Value'))
352 {
353 return $result->Value;
354 }
355 else
356 {
357 return false;
358 }
359 }
360
361 /**
362 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
363 * reporting this value please return an empty string.
364 *
365 * @return string
366 */
367 public function getConnectionCollation()
368 {
369 $this->connect();
370
371 // Attempt to get the database collation by accessing the server system variable.
372 $this->setQuery('SHOW VARIABLES LIKE "collation_connection"');
373 $result = $this->loadObject();
374
375 if (property_exists($result, 'Value'))
376 {
377 return $result->Value;
378 }
379 else
380 {
381 return false;
382 }
383 }
384
385 /**
386 * Get the number of returned rows for the previous executed SQL statement.
387 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
388 * To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
389 *
390 * @param resource $cursor An optional database cursor resource to extract the row count from.
391 *
392 * @return integer The number of returned rows.
393 *
394 * @since 12.1
395 */
396 public function getNumRows($cursor = null)
397 {
398 return mysqli_num_rows($cursor ? $cursor : $this->cursor);
399 }
400
401 /**
402 * Shows the table CREATE statement that creates the given tables.
403 *
404 * @param mixed $tables A table name or a list of table names.
405 *
406 * @return array A list of the create SQL for the tables.
407 *
408 * @since 12.1
409 * @throws RuntimeException
410 */
411 public function getTableCreate($tables)
412 {
413 $this->connect();
414
415 $result = array();
416
417 // Sanitize input to an array and iterate over the list.
418 settype($tables, 'array');
419
420 foreach ($tables as $table)
421 {
422 // Set the query to get the table CREATE statement.
423 $this->setQuery('SHOW CREATE table ' . $this->quoteName($this->escape($table)));
424 $row = $this->loadRow();
425
426 // Populate the result array based on the create statements.
427 $result[$table] = $row[1];
428 }
429
430 return $result;
431 }
432
433 /**
434 * Retrieves field information about a given table.
435 *
436 * @param string $table The name of the database table.
437 * @param boolean $typeOnly True to only return field types.
438 *
439 * @return array An array of fields for the database table.
440 *
441 * @since 12.2
442 * @throws RuntimeException
443 */
444 public function getTableColumns($table, $typeOnly = true)
445 {
446 $this->connect();
447
448 $result = array();
449
450 // Set the query to get the table fields statement.
451 $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table)));
452 $fields = $this->loadObjectList();
453
454 // If we only want the type as the value add just that to the list.
455 if ($typeOnly)
456 {
457 foreach ($fields as $field)
458 {
459 $result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type);
460 }
461 }
462 // If we want the whole field data object add that to the list.
463 else
464 {
465 foreach ($fields as $field)
466 {
467 $result[$field->Field] = $field;
468 }
469 }
470
471 return $result;
472 }
473
474 /**
475 * Get the details list of keys for a table.
476 *
477 * @param string $table The name of the table.
478 *
479 * @return array An array of the column specification for the table.
480 *
481 * @since 12.2
482 * @throws RuntimeException
483 */
484 public function getTableKeys($table)
485 {
486 $this->connect();
487
488 // Get the details columns information.
489 $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table));
490 $keys = $this->loadObjectList();
491
492 return $keys;
493 }
494
495 /**
496 * Method to get an array of all tables in the database.
497 *
498 * @return array An array of all the tables in the database.
499 *
500 * @since 12.2
501 * @throws RuntimeException
502 */
503 public function getTableList()
504 {
505 $this->connect();
506
507 // Set the query to get the tables statement.
508 $this->setQuery('SHOW TABLES');
509 $tables = $this->loadColumn();
510
511 return $tables;
512 }
513
514 /**
515 * Get the version of the database connector.
516 *
517 * @return string The database connector version.
518 *
519 * @since 12.1
520 */
521 public function getVersion()
522 {
523 $this->connect();
524
525 return mysqli_get_server_info($this->connection);
526 }
527
528 /**
529 * Method to get the auto-incremented value from the last INSERT statement.
530 *
531 * @return mixed The value of the auto-increment field from the last inserted row.
532 * If the value is greater than maximal int value, it will return a string.
533 *
534 * @since 12.1
535 */
536 public function insertid()
537 {
538 $this->connect();
539
540 return mysqli_insert_id($this->connection);
541 }
542
543 /**
544 * Locks a table in the database.
545 *
546 * @param string $table The name of the table to unlock.
547 *
548 * @return JDatabaseDriverMysqli Returns this object to support chaining.
549 *
550 * @since 12.2
551 * @throws RuntimeException
552 */
553 public function lockTable($table)
554 {
555 $this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute();
556
557 return $this;
558 }
559
560 /**
561 * Execute the SQL statement.
562 *
563 * @return mixed A database cursor resource on success, boolean false on failure.
564 *
565 * @since 12.1
566 * @throws RuntimeException
567 */
568 public function execute()
569 {
570 $this->connect();
571
572 // Take a local copy so that we don't modify the original query and cause issues later
573 $query = $this->replacePrefix((string) $this->sql);
574
575 if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
576 {
577 $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
578 }
579
580 if (!is_object($this->connection))
581 {
582 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
583 throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
584 }
585
586 // Increment the query counter.
587 $this->count++;
588
589 // Reset the error values.
590 $this->errorNum = 0;
591 $this->errorMsg = '';
592 $memoryBefore = null;
593
594 // If debugging is enabled then let's log the query.
595 if ($this->debug)
596 {
597 // Add the query to the object queue.
598 $this->log[] = $query;
599
600 JLog::add($query, JLog::DEBUG, 'databasequery');
601
602 $this->timings[] = microtime(true);
603
604 if (is_object($this->cursor))
605 {
606 // Avoid warning if result already freed by third-party library
607 @$this->freeResult();
608 }
609
610 $memoryBefore = memory_get_usage();
611 }
612
613 // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
614 $this->cursor = @mysqli_query($this->connection, $query);
615
616 if ($this->debug)
617 {
618 $this->timings[] = microtime(true);
619
620 if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
621 {
622 $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
623 }
624 else
625 {
626 $this->callStacks[] = debug_backtrace();
627 }
628
629 $this->callStacks[count($this->callStacks) - 1][0]['memory'] = array(
630 $memoryBefore,
631 memory_get_usage(),
632 is_object($this->cursor) ? $this->getNumRows() : null,
633 );
634 }
635
636 // If an error occurred handle it.
637 if (!$this->cursor)
638 {
639 // Get the error number and message before we execute any more queries.
640 $this->errorNum = $this->getErrorNumber();
641 $this->errorMsg = $this->getErrorMessage();
642
643 // Check if the server was disconnected.
644 if (!$this->connected())
645 {
646 try
647 {
648 // Attempt to reconnect.
649 $this->connection = null;
650 $this->connect();
651 }
652 // If connect fails, ignore that exception and throw the normal exception.
653 catch (RuntimeException $e)
654 {
655 // Get the error number and message.
656 $this->errorNum = $this->getErrorNumber();
657 $this->errorMsg = $this->getErrorMessage();
658
659 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
660
661 throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
662 }
663
664 // Since we were able to reconnect, run the query again.
665 return $this->execute();
666 }
667 // The server was not disconnected.
668 else
669 {
670 JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
671
672 throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
673 }
674 }
675
676 return $this->cursor;
677 }
678
679 /**
680 * Renames a table in the database.
681 *
682 * @param string $oldTable The name of the table to be renamed
683 * @param string $newTable The new name for the table.
684 * @param string $backup Not used by MySQL.
685 * @param string $prefix Not used by MySQL.
686 *
687 * @return JDatabaseDriverMysqli Returns this object to support chaining.
688 *
689 * @since 12.2
690 * @throws RuntimeException
691 */
692 public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
693 {
694 $this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->execute();
695
696 return $this;
697 }
698
699 /**
700 * Select a database for use.
701 *
702 * @param string $database The name of the database to select for use.
703 *
704 * @return boolean True if the database was successfully selected.
705 *
706 * @since 12.1
707 * @throws RuntimeException
708 */
709 public function select($database)
710 {
711 $this->connect();
712
713 if (!$database)
714 {
715 return false;
716 }
717
718 if (!mysqli_select_db($this->connection, $database))
719 {
720 throw new JDatabaseExceptionConnecting('Could not connect to database.');
721 }
722
723 return true;
724 }
725
726 /**
727 * Set the connection to use UTF-8 character encoding.
728 *
729 * @return boolean True on success.
730 *
731 * @since 12.1
732 */
733 public function setUtf()
734 {
735 // If UTF is not supported return false immediately
736 if (!$this->utf)
737 {
738 return false;
739 }
740
741 // Make sure we're connected to the server
742 $this->connect();
743
744 // Which charset should I use, plain utf8 or multibyte utf8mb4?
745 $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
746
747 $result = @$this->connection->set_charset($charset);
748
749 /**
750 * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise.
751 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd
752 * masks the server version and reports only its own we can not be sure if the server actually does support
753 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we
754 * catch the error and determine that utf8mb4 is not supported!
755 */
756 if (!$result && $this->utf8mb4)
757 {
758 $this->utf8mb4 = false;
759 $result = @$this->connection->set_charset('utf8');
760 }
761
762 return $result;
763 }
764
765 /**
766 * Method to commit a transaction.
767 *
768 * @param boolean $toSavepoint If true, commit to the last savepoint.
769 *
770 * @return void
771 *
772 * @since 12.2
773 * @throws RuntimeException
774 */
775 public function transactionCommit($toSavepoint = false)
776 {
777 $this->connect();
778
779 if (!$toSavepoint || $this->transactionDepth <= 1)
780 {
781 if ($this->setQuery('COMMIT')->execute())
782 {
783 $this->transactionDepth = 0;
784 }
785
786 return;
787 }
788
789 $this->transactionDepth--;
790 }
791
792 /**
793 * Method to roll back a transaction.
794 *
795 * @param boolean $toSavepoint If true, rollback to the last savepoint.
796 *
797 * @return void
798 *
799 * @since 12.2
800 * @throws RuntimeException
801 */
802 public function transactionRollback($toSavepoint = false)
803 {
804 $this->connect();
805
806 if (!$toSavepoint || $this->transactionDepth <= 1)
807 {
808 if ($this->setQuery('ROLLBACK')->execute())
809 {
810 $this->transactionDepth = 0;
811 }
812
813 return;
814 }
815
816 $savepoint = 'SP_' . ($this->transactionDepth - 1);
817 $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));
818
819 if ($this->execute())
820 {
821 $this->transactionDepth--;
822 }
823 }
824
825 /**
826 * Method to initialize a transaction.
827 *
828 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
829 *
830 * @return void
831 *
832 * @since 12.2
833 * @throws RuntimeException
834 */
835 public function transactionStart($asSavepoint = false)
836 {
837 $this->connect();
838
839 if (!$asSavepoint || !$this->transactionDepth)
840 {
841 if ($this->setQuery('START TRANSACTION')->execute())
842 {
843 $this->transactionDepth = 1;
844 }
845
846 return;
847 }
848
849 $savepoint = 'SP_' . $this->transactionDepth;
850 $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));
851
852 if ($this->execute())
853 {
854 $this->transactionDepth++;
855 }
856 }
857
858 /**
859 * Method to fetch a row from the result set cursor as an array.
860 *
861 * @param mixed $cursor The optional result set cursor from which to fetch the row.
862 *
863 * @return mixed Either the next row from the result set or false if there are no more rows.
864 *
865 * @since 12.1
866 */
867 protected function fetchArray($cursor = null)
868 {
869 return mysqli_fetch_row($cursor ? $cursor : $this->cursor);
870 }
871
872 /**
873 * Method to fetch a row from the result set cursor as an associative array.
874 *
875 * @param mixed $cursor The optional result set cursor from which to fetch the row.
876 *
877 * @return mixed Either the next row from the result set or false if there are no more rows.
878 *
879 * @since 12.1
880 */
881 protected function fetchAssoc($cursor = null)
882 {
883 return mysqli_fetch_assoc($cursor ? $cursor : $this->cursor);
884 }
885
886 /**
887 * Method to fetch a row from the result set cursor as an object.
888 *
889 * @param mixed $cursor The optional result set cursor from which to fetch the row.
890 * @param string $class The class name to use for the returned row object.
891 *
892 * @return mixed Either the next row from the result set or false if there are no more rows.
893 *
894 * @since 12.1
895 */
896 protected function fetchObject($cursor = null, $class = 'stdClass')
897 {
898 return mysqli_fetch_object($cursor ? $cursor : $this->cursor, $class);
899 }
900
901 /**
902 * Method to free up the memory used for the result set.
903 *
904 * @param mixed $cursor The optional result set cursor from which to fetch the row.
905 *
906 * @return void
907 *
908 * @since 12.1
909 */
910 protected function freeResult($cursor = null)
911 {
912 mysqli_free_result($cursor ? $cursor : $this->cursor);
913
914 if ((! $cursor) || ($cursor === $this->cursor))
915 {
916 $this->cursor = null;
917 }
918 }
919
920 /**
921 * Unlocks tables in the database.
922 *
923 * @return JDatabaseDriverMysqli Returns this object to support chaining.
924 *
925 * @since 12.1
926 * @throws RuntimeException
927 */
928 public function unlockTables()
929 {
930 $this->setQuery('UNLOCK TABLES')->execute();
931
932 return $this;
933 }
934
935 /**
936 * Internal function to check if profiling is available
937 *
938 * @return boolean
939 *
940 * @since 3.1.3
941 */
942 private function hasProfiling()
943 {
944 try
945 {
946 $res = mysqli_query($this->connection, "SHOW VARIABLES LIKE 'have_profiling'");
947 $row = mysqli_fetch_assoc($res);
948
949 return isset($row);
950 }
951 catch (Exception $e)
952 {
953 return false;
954 }
955 }
956
957 /**
958 * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation?
959 *
960 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
961 *
962 * @return boolean
963 *
964 * @since CMS 3.5.0
965 */
966 private function serverClaimsUtf8mb4Support()
967 {
968 $client_version = mysqli_get_client_info();
969 $server_version = $this->getVersion();
970
971 if (version_compare($server_version, '5.5.3', '<'))
972 {
973 return false;
974 }
975 else
976 {
977 if (strpos($client_version, 'mysqlnd') !== false)
978 {
979 $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);
980
981 return version_compare($client_version, '5.0.9', '>=');
982 }
983 else
984 {
985 return version_compare($client_version, '5.5.3', '>=');
986 }
987 }
988 }
989
990 /**
991 * Return the actual SQL Error number
992 *
993 * @return integer The SQL Error number
994 *
995 * @since 3.4.6
996 */
997 protected function getErrorNumber()
998 {
999 return (int) mysqli_errno($this->connection);
1000 }
1001
1002 /**
1003 * Return the actual SQL Error message
1004 *
1005 * @return string The SQL Error message
1006 *
1007 * @since 3.4.6
1008 */
1009 protected function getErrorMessage()
1010 {
1011 $errorMessage = (string) mysqli_error($this->connection);
1012
1013 // Replace the Databaseprefix with `#__` if we are not in Debug
1014 if (!$this->debug)
1015 {
1016 $errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
1017 }
1018
1019 return $errorMessage;
1020 }
1021 }
1022