disconnect(); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 12.1 * @throws RuntimeException */ public function connect() { if ($this->connection) { return; } /* * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we * have to extract them from the host string. */ $port = isset($this->options['port']) ? $this->options['port'] : 3306; if (preg_match('/^(?P((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.+))?$/', $this->options['host'], $matches)) { // It's an IPv4 address with ot without port $this->options['host'] = $matches['host']; if (!empty($matches['port'])) { $port = $matches['port']; } } elseif (preg_match('/^(?P\[.*\])(:(?P.+))?$/', $this->options['host'], $matches)) { // We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306 $this->options['host'] = $matches['host']; if (!empty($matches['port'])) { $port = $matches['port']; } } elseif (preg_match('/^(?P(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P[^:]+))?$/i', $this->options['host'], $matches)) { // Named host (e.g domain.com or localhost) with ot without port $this->options['host'] = $matches['host']; if (!empty($matches['port'])) { $port = $matches['port']; } } elseif (preg_match('/^:(?P[^:]+)$/', $this->options['host'], $matches)) { // Empty host, just port, e.g. ':3306' $this->options['host'] = 'localhost'; $port = $matches['port']; } // ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default // Get the port number or socket name if (is_numeric($port)) { $this->options['port'] = (int) $port; } else { $this->options['socket'] = $port; } // Make sure the MySQLi extension for PHP is installed and enabled. if (!function_exists('mysqli_connect')) { throw new RuntimeException('The MySQL adapter mysqli is not available'); } $this->connection = @mysqli_connect( $this->options['host'], $this->options['user'], $this->options['password'], null, $this->options['port'], $this->options['socket'] ); // Attempt to connect to the server. if (!$this->connection) { throw new RuntimeException('Could not connect to MySQL.'); } // Set sql_mode to non_strict mode mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';"); // If auto-select is enabled select the given database. if ($this->options['select'] && !empty($this->options['database'])) { $this->select($this->options['database']); } // Set charactersets (needed for MySQL 4.1.2+). $this->setUTF(); // Turn MySQL profiling ON in debug mode: if ($this->debug && $this->hasProfiling()) { mysqli_query($this->connection, "SET profiling_history_size = 100;"); mysqli_query($this->connection, "SET profiling = 1;"); } } /** * Disconnects the database. * * @return void * * @since 12.1 */ public function disconnect() { // Close the connection. if ($this->connection) { foreach ($this->disconnectHandlers as $h) { call_user_func_array($h, array( &$this)); } mysqli_close($this->connection); } $this->connection = null; } /** * Method to escape a string for usage in an SQL statement. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 12.1 */ public function escape($text, $extra = false) { $this->connect(); $result = mysqli_real_escape_string($this->getConnection(), $text); if ($extra) { $result = addcslashes($result, '%_'); } return $result; } /** * Test to see if the MySQL connector is available. * * @return boolean True on success, false otherwise. * * @since 12.1 */ public static function isSupported() { return (function_exists('mysqli_connect')); } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 12.1 */ public function connected() { if (is_object($this->connection)) { return mysqli_ping($this->connection); } return false; } /** * Drops a table from the database. * * @param string $tableName The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return JDatabaseDriverMysqli Returns this object to support chaining. * * @since 12.2 * @throws RuntimeException */ public function dropTable($tableName, $ifExists = true) { $this->connect(); $query = $this->getQuery(true); $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName)); $this->execute(); return $this; } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. * * @since 12.1 */ public function getAffectedRows() { $this->connect(); return mysqli_affected_rows($this->connection); } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return mixed The collation in use by the database (string) or boolean false if not supported. * * @since 12.2 * @throws RuntimeException */ public function getCollation() { $this->connect(); $tables = $this->getTableList(); /* @todo: $this->quoteName($tables[0]) is not the best solution as we test possible on a non-Joomla table if we have e.g. different tables (application) in the same database. */ $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($tables[0])); $array = $this->loadAssocList(); foreach ($array as $field) { if (!is_null($field['Collation'])) { return $field['Collation']; } } return null; } /** * Get the number of returned rows for the previous executed SQL statement. * * @param resource $cursor An optional database cursor resource to extract the row count from. * * @return integer The number of returned rows. * * @since 12.1 */ public function getNumRows($cursor = null) { return mysqli_num_rows($cursor ? $cursor : $this->cursor); } /** * Shows the table CREATE statement that creates the given tables. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 12.1 * @throws RuntimeException */ public function getTableCreate($tables) { $this->connect(); $result = array(); // Sanitize input to an array and iterate over the list. settype($tables, 'array'); foreach ($tables as $table) { // Set the query to get the table CREATE statement. $this->setQuery('SHOW CREATE table ' . $this->quoteName($this->escape($table))); $row = $this->loadRow(); // Populate the result array based on the create statements. $result[$table] = $row[1]; } return $result; } /** * Retrieves field information about a given table. * * @param string $table The name of the database table. * @param boolean $typeOnly True to only return field types. * * @return array An array of fields for the database table. * * @since 12.2 * @throws RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $this->connect(); $result = array(); // Set the query to get the table fields statement. $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table))); $fields = $this->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type); } } // If we want the whole field data object add that to the list. else { foreach ($fields as $field) { $result[$field->Field] = $field; } } return $result; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 12.2 * @throws RuntimeException */ public function getTableKeys($table) { $this->connect(); // Get the details columns information. $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table)); $keys = $this->loadObjectList(); return $keys; } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 12.2 * @throws RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. $this->setQuery('SHOW TABLES'); $tables = $this->loadColumn(); return $tables; } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 12.1 */ public function getVersion() { $this->connect(); return mysqli_get_server_info($this->connection); } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return mixed The value of the auto-increment field from the last inserted row. * If the value is greater than maximal int value, it will return a string. * * @since 12.1 */ public function insertid() { $this->connect(); return mysqli_insert_id($this->connection); } /** * Locks a table in the database. * * @param string $table The name of the table to unlock. * * @return JDatabaseDriverMysqli Returns this object to support chaining. * * @since 12.2 * @throws RuntimeException */ public function lockTable($table) { $this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute(); return $this; } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. * * @since 12.1 * @throws RuntimeException */ public function execute() { $this->connect(); if (!is_object($this->connection)) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database'); throw new RuntimeException($this->errorMsg, $this->errorNum); } // Take a local copy so that we don't modify the original query and cause issues later $query = $this->replacePrefix((string) $this->sql); if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0)) { $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit; } // Increment the query counter. $this->count++; // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; $memoryBefore = null; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; JLog::add($query, JLog::DEBUG, 'databasequery'); $this->timings[] = microtime(true); if (is_object($this->cursor)) { // Avoid warning if result already freed by third-party library @$this->freeResult(); } $memoryBefore = memory_get_usage(); } // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. $this->cursor = @mysqli_query($this->connection, $query); if ($this->debug) { $this->timings[] = microtime(true); if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) { $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); } else { $this->callStacks[] = debug_backtrace(); } $this->callStacks[count($this->callStacks) - 1][0]['memory'] = array($memoryBefore, memory_get_usage(), is_object($this->cursor) ? $this->getNumRows() : null); } // If an error occurred handle it. if (!$this->cursor) { $this->errorNum = (int) mysqli_errno($this->connection); $this->errorMsg = (string) mysqli_error($this->connection) . ' SQL=' . $query; // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'databasequery'); throw new RuntimeException($this->errorMsg, $this->errorNum); } // Since we were able to reconnect, run the query again. return $this->execute(); } // The server was not disconnected. else { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'databasequery'); throw new RuntimeException($this->errorMsg, $this->errorNum); } } return $this->cursor; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Not used by MySQL. * @param string $prefix Not used by MySQL. * * @return JDatabaseDriverMysqli Returns this object to support chaining. * * @since 12.2 * @throws RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->execute(); return $this; } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 12.1 * @throws RuntimeException */ public function select($database) { $this->connect(); if (!$database) { return false; } if (!mysqli_select_db($this->connection, $database)) { throw new RuntimeException('Could not connect to database.'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 12.1 */ public function setUTF() { $this->connect(); return $this->connection->set_charset('utf8'); } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 12.2 * @throws RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('COMMIT')->execute()) { $this->transactionDepth = 0; } return; } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 12.2 * @throws RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('ROLLBACK')->execute()) { $this->transactionDepth = 0; } return; } $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth--; } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 12.2 * @throws RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { if ($this->setQuery('START TRANSACTION')->execute()) { $this->transactionDepth = 1; } return; } $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } /** * Method to fetch a row from the result set cursor as an array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchArray($cursor = null) { return mysqli_fetch_row($cursor ? $cursor : $this->cursor); } /** * Method to fetch a row from the result set cursor as an associative array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchAssoc($cursor = null) { return mysqli_fetch_assoc($cursor ? $cursor : $this->cursor); } /** * Method to fetch a row from the result set cursor as an object. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * @param string $class The class name to use for the returned row object. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchObject($cursor = null, $class = 'stdClass') { return mysqli_fetch_object($cursor ? $cursor : $this->cursor, $class); } /** * Method to free up the memory used for the result set. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return void * * @since 12.1 */ protected function freeResult($cursor = null) { mysqli_free_result($cursor ? $cursor : $this->cursor); if ((! $cursor) || ($cursor === $this->cursor)) { $this->cursor = null; } } /** * Unlocks tables in the database. * * @return JDatabaseDriverMysqli Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function unlockTables() { $this->setQuery('UNLOCK TABLES')->execute(); return $this; } /** * Internal function to check if profiling is available * * @return boolean * * @since 3.1.3 */ private function hasProfiling() { try { $res = mysqli_query($this->connection, "SHOW VARIABLES LIKE 'have_profiling'"); $row = mysqli_fetch_assoc($res); return isset($row); } catch (Exception $e) { return false; } } }