Файловый менеджер - Редактировать - /home/wuectly/www/03cbe/BackupEngine.tar
Назад
Platform.php 0000604 00000020161 15245670452 0007047 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Platform\Base; use Akeeba\Engine\Platform\PlatformInterface; use DirectoryIterator; use Exception; /** * Platform abstraction. Manages the loading of platform connector objects and delegates calls to itself the them. * * @property string $tableNameProfiles The name of the table where backup profiles are stored * @property string $tableNameStats The name of the table where backup records are stored * * @since 3.4 */ class Platform { /** @var Base|null The currently loaded platform connector object instance */ protected static $platformConnectorInstance = null; /** @var array A list of additional directories where platform classes can be found */ protected static $knownPlatformsDirectories = []; /** @var Platform The currently loaded object instance of this class. WARNING: This is NOT the platform connector! */ protected static $instance = null; /** * Public class constructor * * @param string $platform Optional; platform name. Leave blank to auto-detect. * * @throws Exception When the platform cannot be loaded */ public function __construct($platform = null) { if (empty($platform) || is_null($platform)) { $platform = static::detectPlatform(); } if (empty($platform)) { throw new Exception('Can not find a suitable Akeeba Engine platform for your site'); } static::$platformConnectorInstance = static::loadPlatform($platform); if (!is_object(static::$platformConnectorInstance)) { throw new Exception("Can not load Akeeba Engine platform $platform"); } } /** * Implements the Singleton pattern for this class * * @staticvar Platform $instance The static object instance * * @param string $platform Optional; platform name. Autodetect if blank. * * @return PlatformInterface */ public static function &getInstance($platform = null) { if (!is_object(static::$instance)) { static::$instance = new Platform($platform); } return static::$instance; } /** * Get a list of all directories where platform classes can be found * * @return array */ public static function getPlatformDirectories() { $defaultPath = []; if (is_object(static::$platformConnectorInstance)) { $defaultPath[] = __DIR__ . '/Platform/' . static::$platformConnectorInstance->platformName; } return array_merge( static::$knownPlatformsDirectories, $defaultPath ); } /** * Lists available platforms * * @staticvar array $platforms Static cache of the available platforms * * @return array The list of available platforms */ static public function listPlatforms() { if (empty(static::$knownPlatformsDirectories)) { $di = new DirectoryIterator(__DIR__ . '/Platform'); /** @var DirectoryIterator $file */ foreach ($di as $file) { if (!$file->isDir()) { continue; } if ($file->isDot()) { continue; } if ($file->getExtension() !== 'php') { continue; } $shortName = $file->getFilename(); $bareName = basename($shortName, '.php'); /** * We never have dots in our filenames but some hosts will rename files similar to foo.1.php when their * broken security scanners detect a false positive. This is our defence against that. */ if (strpos($bareName, '.') !== false) { continue; } static::$knownPlatformsDirectories[$shortName] = $file->getRealPath(); } } return static::$knownPlatformsDirectories; } /** * Add a platform to the list of known platforms * * @param string $slug Short name of the platform * @param string $platformDirectory The path where you can find it * * @return void */ public static function addPlatform($slug, $platformDirectory) { if (empty(static::$knownPlatformsDirectories)) { static::listPlatforms(); static::$knownPlatformsDirectories[$slug] = $platformDirectory; } } /** * Auto-detect the suitable platform for this site * * @return string * * @throws Exception When no platform is detected */ protected static function detectPlatform() { $platforms = static::listPlatforms(); if (empty($platforms)) { throw new Exception('No Akeeba Engine platform class found'); } $bestPlatform = (object) [ 'name' => null, 'priority' => 0, ]; foreach ($platforms as $platform => $path) { $o = static::loadPlatform($platform, $path); if (is_null($o)) { continue; } if ($o->isThisPlatform()) { if ($o->priority > $bestPlatform->priority) { $bestPlatform->priority = $o->priority; $bestPlatform->name = $platform; } } } return $bestPlatform->name; } /** * Load a given platform and return the platform object * * @param string $platform Platform name * @param string $path The path to laod the platform from (optional) * * @return Base */ protected static function &loadPlatform($platform, $path = null) { if (empty($path)) { if (isset(static::$knownPlatformsDirectories[$platform])) { $path = static::$knownPlatformsDirectories[$platform]; } } if (empty($path)) { $path = dirname(__FILE__) . '/' . $platform; } $classFile = $path . '/Platform.php'; $className = '\\Akeeba\\Engine\\Platform\\' . ucfirst($platform); $null = null; if (!file_exists($classFile)) { return $null; } require_once($classFile); if (!class_exists($className, false)) { return $null; } $o = new $className; return $o; } /** * Magic method to proxy all calls to the loaded platform object * * @param string $name The name of the method to call * @param array $arguments The arguments to pass * * @return mixed The result of the method being called * * @throws Exception When the platform isn't loaded or an non-existent method is called */ public function __call($name, array $arguments) { if (is_null(static::$platformConnectorInstance)) { throw new Exception('Akeeba Engine platform is not loaded'); } if (method_exists(static::$platformConnectorInstance, $name)) { return static::$platformConnectorInstance->$name(...$arguments); } else { throw new Exception('Method ' . $name . ' not found in Akeeba Platform'); } } /** * Magic getter for the properties of the loaded platform * * @param string $name The name of the property to get * * @return mixed The value of the property */ public function __get($name) { if (!isset(static::$platformConnectorInstance->$name) || !property_exists(static::$platformConnectorInstance, $name)) { static::$platformConnectorInstance->$name = null; user_error(__CLASS__ . ' does not support property ' . $name, E_NOTICE); } return static::$platformConnectorInstance->$name; } /** * Magic setter for the properties of the loaded platform * * @param string $name The name of the property to set * @param mixed $value The value of the property to set */ public function __set($name, $value) { if (isset(static::$platformConnectorInstance->$name) || property_exists(static::$platformConnectorInstance, $name)) { static::$platformConnectorInstance->$name = $value; } else { static::$platformConnectorInstance->$name = null; user_error(__CLASS__ . ' does not support property ' . $name, E_NOTICE); } } } Driver/None.php 0000604 00000021534 15245670452 0007422 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Base as QueryBase; /** * Dummy driver class for flat-file CMS */ #[\AllowDynamicProperties] class None extends Base { public static $dbtech = 'none'; /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'none'; public function __construct(array $options) { $this->driverType = 'none'; parent::__construct($options); } /** * Test to see if this db driver is available * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return true; } public function open() { return $this; } /** * Closes the database connection */ public function close() { return; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. */ public function connected() { return true; } /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return Base Returns this object to support chaining. */ public function dropTable($table, $ifExists = true) { return $this; } /** * 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. */ public function escape($text, $extra = false) { return ''; } /** * 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. */ public function fetchAssoc($cursor = null) { return false; } /** * 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 */ public function freeResult($cursor = null) { return; } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. */ public function getAffectedRows() { return 0; } /** * 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 or boolean false if not supported. */ public function getCollation() { return false; } /** * 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. */ public function getNumRows($cursor = null) { return 0; } /** * Get the current query object or a new QueryBase object. * * @param boolean $new False to return the current query object, True to return a new QueryBase object. * * @return QueryBase The current query object or a new object extending the QueryBase class. */ public function getQuery($new = false) { return $this->sql; } public function createQuery() { return $this->sql; } /** * Retrieves field information about the given tables. * * @param string $table The name of the database table. * @param boolean $typeOnly True (default) to only return field types. * * @return array An array of fields by table. */ public function getTableColumns($table, $typeOnly = true) { return []; } /** * 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. */ public function getTableCreate($tables) { return []; } /** * Retrieves field information about the given tables. * * @param mixed $tables A table name or a list of table names. * * @return array An array of keys for the table(s). */ public function getTableKeys($tables) { return []; } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. */ public function getTableList() { return []; } /** * Returns an array with the names of tables, views, procedures, functions and triggers * in the database. The table names are the keys of the tables, whereas the value is * the type of each element: table, view, merge, temp, procedure, function or trigger. * Note that merge are MRG_MYISAM tables and temp is non-permanent data table, usually * set up as temporary, black hole or federated tables. These two types should never, * ever, have their data dumped in the SQL dump file. * * @param bool $abstract Return or normal names? Defaults to true (names) * * @return array */ public function getTables($abstract = true) { return []; } /** * Get the version of the database connector * * @return string The database connector version. */ public function getVersion() { return '0.0.0'; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. */ public function insertid() { return 0; } /** * Locks a table in the database. * * @param string $tableNameName The name of the table to unlock. * * @return Base Returns this object to support chaining. */ public function lockTable($tableNameName) { return $this; } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. */ public function query() { return false; } /** * 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 Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return Base Returns this object to support chaining. */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { 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. */ public function select($database) { return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. */ public function setUTF() { return true; } /** * Method to commit a transaction. * * @return void */ public function transactionCommit() { return; } /** * Method to roll back a transaction. * * @return void */ public function transactionRollback() { return; } /** * Method to initialize a transaction. * * @return void */ public function transactionStart() { return; } /** * Unlocks tables in the database. * * @return Base Returns this object to support chaining. */ public function unlockTables() { return $this; } /** * 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. */ protected function fetchArray($cursor = null) { return false; } /** * 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. */ protected function fetchObject($cursor = null, $class = 'stdClass') { return false; } } Driver/Sqlite.php 0000604 00000047176 15245670452 0007776 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Base as QueryBase; use Akeeba\Engine\Driver\Query\Limitable; use Akeeba\Engine\Driver\Query\Preparable; use PDO; use PDOException; use PDOStatement; use RuntimeException; use SQLite3; /** * SQLite database driver supporting PDO based connections * * @see http://php.net/manual/en/ref.pdo-sqlite.php * @since 1.0 */ #[\AllowDynamicProperties] class Sqlite extends Base { public static $dbtech = 'sqlite'; /** * The name of the database driver. * * @var string * @since 1.0 */ public $name = 'sqlite'; /** @var PDOStatement The database connection cursor from the last query. */ protected $cursor; /** @var array Contains the current query execution status */ protected $executed = false; /** * The character(s) used to quote SQL statement names such as table names or field names, * etc. The child classes should define this as necessary. If a single character string the * same character is used for both sides of the quoted name, else the first character will be * used for the opening quote and the second for the closing quote. * * @var string * @since 1.0 */ protected $nameQuote = '`'; /** @var resource The prepared statement. */ protected $prepared; /** @var bool Are we in the process of reconnecting to the database server? */ private $isReconnecting = false; public function __construct(array $options) { $this->driverType = 'sqlite'; parent::__construct($options); if (!is_object($this->connection)) { $this->open(); } } /** * Test to see if the PDO ODBC connector is available. * * @return boolean True on success, false otherwise. * * @since 1.0 */ public static function isSupported() { return class_exists('\\PDO') && in_array('sqlite', PDO::getAvailableDrivers()); } /** * Destructor. * * @since 1.0 */ public function __destruct() { $this->freeResult(); unset($this->connection); } public function close() { $return = false; if (is_object($this->cursor)) { $this->cursor->closeCursor(); } $this->connection = null; return $return; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. */ public function connected() { return !empty($this->connection); } /** * Disconnects the database. * * @return void * * @since 1.0 */ public function disconnect() { $this->freeResult(); unset($this->connection); } /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return Sqlite Returns this object to support chaining. * * @since 1.0 */ public function dropTable($table, $ifExists = true) { $this->open(); $query = $this->getQuery(true); $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($table)); $this->execute(); return $this; } /** * Method to escape a string for usage in an SQLite statement. * * Note: Using query objects with bound variables is preferable to the below. * * @param string $text The string to be escaped. * @param boolean $extra Unused optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 1.0 */ public function escape($text, $extra = false) { if (is_int($text) || is_float($text)) { return $text; } if (is_null($text)) { return 'NULL'; } return SQLite3::escapeString($text); } public function fetchAssoc($cursor = null) { if (!empty($cursor) && $cursor instanceof PDOStatement) { return $cursor->fetch(PDO::FETCH_ASSOC); } if ($this->prepared instanceof PDOStatement) { return $this->prepared->fetch(PDO::FETCH_ASSOC); } } public function freeResult($cursor = null) { $this->executed = false; if ($cursor instanceof PDOStatement) { $cursor->closeCursor(); $cursor = null; } if ($this->prepared instanceof PDOStatement) { $this->prepared->closeCursor(); $this->prepared = null; } } public function getAffectedRows() { $this->open(); if ($this->prepared instanceof PDOStatement) { return $this->prepared->rowCount(); } else { return 0; } } /** * 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 or boolean false if not supported. * * @since 1.0 */ public function getCollation() { return $this->charset; } public function getNumRows($cursor = null) { $this->open(); if ($cursor instanceof PDOStatement) { return $cursor->rowCount(); } elseif ($this->prepared instanceof PDOStatement) { return $this->prepared->rowCount(); } else { return 0; } } /** * Retrieve a PDO database connection attribute * http://www.php.net/manual/en/pdo.getattribute.php * * Usage: $db->getOption(PDO::ATTR_CASE); * * @param mixed $key One of the PDO::ATTR_* Constants * * @return mixed * * @since 1.0 */ public function getOption($key) { $this->open(); return $this->connection->getAttribute($key); } /** * Get the current query object or a new Query object. * We have to override the parent method since it will always return a PDO query, while we have a * specialized class for SQLite * * @param boolean $new False to return the current query object, True to return a new Query object. * * @return QueryBase The current query object or a new object extending the Query class. * * @throws RuntimeException */ public function getQuery($new = false) { if ($new) { return new Query\Sqlite($this); } return $this->sql; } public function createQuery() { return new Query\Sqlite($this); } /** * 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. * * @throws RuntimeException * @since 1.0 */ public function getTableColumns($table, $typeOnly = true) { $this->open(); $columns = []; $query = $this->getQuery(true); $fieldCasing = $this->getOption(PDO::ATTR_CASE); $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); $table = strtoupper($table); $query->setQuery('pragma table_info(' . $table . ')'); $this->setQuery($query); $fields = $this->loadObjectList(); if ($typeOnly) { foreach ($fields as $field) { $columns[$field->NAME] = $field->TYPE; } } else { foreach ($fields as $field) { // Do some dirty translation to MySQL output. $columns[$field->NAME] = (object) [ 'Field' => $field->NAME, 'Type' => $field->TYPE, 'Null' => ($field->NOTNULL == '1' ? 'NO' : 'YES'), 'Default' => $field->DFLT_VALUE, 'Key' => ($field->PK == '1' ? 'PRI' : ''), ]; } } $this->setOption(PDO::ATTR_CASE, $fieldCasing); return $columns; } /** * Shows the table CREATE statement that creates the given tables. * * Note: Doesn't appear to have support in SQLite * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @throws RuntimeException * @since 1.0 */ public function getTableCreate($tables) { $this->open(); // Sanitize input to an array and iterate over the list. $tables = (array) $tables; return $tables; } /** * Get the details list of keys for a table. * * @param string $tables The name of the table. * * @return array An array of the column specification for the table. * * @throws RuntimeException * @since 1.0 */ public function getTableKeys($tables) { $this->open(); $keys = []; $query = $this->getQuery(true); $fieldCasing = $this->getOption(PDO::ATTR_CASE); $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); $tables = strtoupper($tables); $query->setQuery('pragma table_info( ' . $tables . ')'); // $query->bind(':tableName', $table); $this->setQuery($query); $rows = $this->loadObjectList(); foreach ($rows as $column) { if ($column->PK == 1) { $keys[$column->NAME] = $column; } } $this->setOption(PDO::ATTR_CASE, $fieldCasing); return $keys; } /** * Method to get an array of all tables in the database (schema). * * @return array An array of all the tables in the database. * * @throws RuntimeException * @since 1.0 */ public function getTableList() { $this->open(); /* @type Query\Sqlite $query */ $query = $this->getQuery(true); $type = 'table'; $query->select('name'); $query->from('sqlite_master'); $query->where('type = :type'); $query->bind(':type', $type); $query->order('name'); $this->setQuery($query); $tables = $this->loadColumn(); return $tables; } /** * There's no point on return "a list of tables" inside a SQLite database: we are simple going to * copy the whole database file in the new location * * @param bool $abstract * * @return array */ public function getTables($abstract = true) { return []; } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 1.0 */ public function getVersion() { $this->open(); $this->setQuery("SELECT sqlite_version()"); return $this->loadResult(); } public function insertid() { $this->open(); // Error suppress this to prevent PDO warning us that the driver doesn't support this operation. return @$this->connection->lastInsertId(); } /** * Locks a table in the database. * * @param string $tableName The name of the table to unlock. * * @return Sqlite Returns this object to support chaining. * * @throws RuntimeException * @since 1.0 */ public function lockTable($tableName) { return $this; } public function open() { if ($this->connected()) { return; } else { $this->close(); } if (isset($this->options['version']) && $this->options['version'] == 2) { $format = 'sqlite2:#DBNAME#'; } else { $format = 'sqlite:#DBNAME#'; } $replace = ['#DBNAME#']; $with = [$this->options['database']]; // Create the connection string: $connectionString = str_replace($replace, $with, $format); try { $this->connection = new PDO( $connectionString, $this->options['user'], $this->options['password'] ); } catch (PDOException $e) { throw new RuntimeException('Could not connect to PDO' . ': ' . $e->getMessage(), 2, $e); } } public function query() { $this->open(); if (!is_object($this->connection)) { throw new RuntimeException($this->errorMsg, $this->errorNum); } // Take a local copy so that we don't modify the original query and cause issues later $sql = $this->replacePrefix((string) $this->sql); if ($this->limit > 0 || $this->offset > 0) { $sql .= ' LIMIT ' . $this->limit; if ($this->offset > 0) { $sql .= ' OFFSET ' . $this->offset; } } // Increment the query counter. $this->count++; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $sql; } // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // Execute the query. $this->executed = false; if ($this->prepared instanceof PDOStatement) { // Bind the variables: if ($this->sql instanceof Preparable) { $bounded =& $this->sql->getBounded(); foreach ($bounded as $key => $obj) { $this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions); } } $this->executed = $this->prepared->execute(); } // If an error occurred handle it. if (!$this->executed) { // Get the error number and message before we execute any more queries. $errorNum = (int) $this->connection->errorCode(); $errorMsg = (string) 'SQL: ' . implode(", ", $this->connection->errorInfo()); // Check if the server was disconnected. if (!$this->connected() && !$this->isReconnecting) { $this->isReconnecting = true; try { // Attempt to reconnect. $this->connection = null; $this->open(); } catch (RuntimeException $e) // If connect fails, ignore that exception and throw the normal exception. { // Get the error number and message. $this->errorNum = (int) $this->connection->errorCode(); $this->errorMsg = (string) 'SQL: ' . implode(", ", $this->connection->errorInfo()); // Throw the normal query exception. throw new RuntimeException($this->errorMsg, $this->errorNum); } // Since we were able to reconnect, run the query again. $result = $this->query(); $this->isReconnecting = false; return $result; } else // The server was not disconnected. { // Get the error number and message from before we tried to reconnect. $this->errorNum = $errorNum; $this->errorMsg = $errorMsg; // Throw the normal query exception. throw new RuntimeException($this->errorMsg, $this->errorNum); } } return $this->prepared; } /** * 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 Sqlite. * @param string $prefix Not used by Sqlite. * * @return Sqlite Returns this object to support chaining. * * @throws RuntimeException * @since 1.0 */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME 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. * * @throws RuntimeException * @since 1.0 */ public function select($database) { $this->open(); $this->_database = $database; return true; } /** * Sets an attribute on the PDO database handle. * http://www.php.net/manual/en/pdo.setattribute.php * * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); * * @param integer $key One of the PDO::ATTR_* Constants * @param mixed $value One of the associated PDO Constants * related to the particular attribute * key. * * @return boolean * * @since 1.0 */ public function setOption($key, $value) { $this->open(); return $this->connection->setAttribute($key, $value); } /** * Sets the SQL statement string for later execution. * * @param mixed $query The SQL statement to set either as a JDatabaseQuery object or a string. * @param integer $offset The affected row offset to set. * @param integer $limit The maximum affected rows to set. * @param array $driverOptions The optional PDO driver options * * @return Base This object to support method chaining. * * @since 1.0 */ public function setQuery($query, $offset = null, $limit = null, $driverOptions = []) { $this->open(); $this->freeResult(); if (is_string($query)) { // Allows taking advantage of bound variables in a direct query: $query = $this->getQuery(true)->setQuery($query); } if ($query instanceof Limitable && !is_null($offset) && !is_null($limit)) { $query->setLimit($limit, $offset); } $sql = $this->replacePrefix((string) $query); $this->prepared = $this->connection->prepare($sql, $driverOptions); // Store reference to the DatabaseQuery instance: parent::setQuery($query, $offset, $limit); return $this; } /** * Set the connection to use UTF-8 character encoding. * * Returns false automatically for the Oracle driver since * you can only set the character set when the connection * is created. * * @return boolean True on success. * * @since 1.0 */ public function setUTF() { $this->open(); return false; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @throws RuntimeException * @since 1.0 */ public function transactionCommit($toSavepoint = false) { $this->open(); if (!$toSavepoint || $this->transactionDepth <= 1) { $this->open(); if (!$toSavepoint || $this->transactionDepth == 1) { $this->connection->commit(); } $this->transactionDepth--; } else { $this->transactionDepth--; } } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @throws RuntimeException * @since 1.0 */ public function transactionRollback($toSavepoint = false) { $this->connected(); if (!$toSavepoint || $this->transactionDepth <= 1) { $this->open(); if (!$toSavepoint || $this->transactionDepth == 1) { $this->connection->rollBack(); } $this->transactionDepth--; } else { $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TO ' . $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 * * @throws RuntimeException * @since 1.0 */ public function transactionStart($asSavepoint = false) { $this->connected(); if (!$asSavepoint || !$this->transactionDepth) { $this->open(); if (!$asSavepoint || !$this->transactionDepth) { $this->connection->beginTransaction(); } $this->transactionDepth++; } else { $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } } /** * Unlocks tables in the database. * * @return Sqlite Returns this object to support chaining. * * @throws RuntimeException * @since 1.0 */ public function unlockTables() { return $this; } protected function fetchArray($cursor = null) { if (!empty($cursor) && $cursor instanceof PDOStatement) { return $cursor->fetch(PDO::FETCH_NUM); } if ($this->prepared instanceof PDOStatement) { return $this->prepared->fetch(PDO::FETCH_NUM); } } protected function fetchObject($cursor = null, $class = 'stdClass') { if (!empty($cursor) && $cursor instanceof PDOStatement) { return $cursor->fetchObject($class); } if ($this->prepared instanceof PDOStatement) { return $this->prepared->fetchObject($class); } } } Driver/Mysql.php 0000604 00000063445 15245670452 0007637 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Mysql as QueryMysql; use Akeeba\Engine\Factory; use Exception; use RuntimeException; /** * MySQL classic driver for Akeeba Engine * * Based on Joomla! Platform 11.2 */ #[\AllowDynamicProperties] class Mysql extends Base { /** * The name of the database driver. * * @var string * @since 11.1 */ public $name = 'mysql'; /** * Hostname * * @var string */ protected $host; /** * The character(s) used to quote SQL statement names such as table names or field names, * etc. The child classes should define this as necessary. If a single character string the * same character is used for both sides of the quoted name, else the first character will be * used for the opening quote and the second for the closing quote. * * @var string * @since 11.1 */ protected $nameQuote = '`'; /** * The null or zero representation of a timestamp for the database driver. This should be * defined in child classes to hold the appropriate value for the engine. * * @var string * @since 11.1 */ protected $nullDate = '0000-00-00 00:00:00'; /** * Password * * @var string */ protected $password; /** * Should I select a database? * * @var bool */ protected $selectDatabase; /** * Username * * @var string */ protected $user; /** @var bool Are we in the process of reconnecting to the database server? */ private $isReconnecting = false; /** @var array|null A cache of the tables contained in the currently connected database */ public $tablesCache = null; /** * Database object constructor * * @param array $options List of options used to configure the connection */ public function __construct($options) { $this->driverType = 'mysql'; // Init $this->nameQuote = '`'; $host = array_key_exists('host', $options) ? $options['host'] : 'localhost'; $port = array_key_exists('port', $options) ? $options['port'] : ''; $user = array_key_exists('user', $options) ? $options['user'] : ''; $password = array_key_exists('password', $options) ? $options['password'] : ''; $database = array_key_exists('database', $options) ? $options['database'] : ''; $prefix = array_key_exists('prefix', $options) ? $options['prefix'] : ''; $select = array_key_exists('select', $options) ? $options['select'] : true; if (!empty($port)) { $host .= ':' . $port; } // finalize initialization parent::__construct($options); // Avoid overwriting connection info if they're already set if (is_null($this->host)) { $this->host = $host; } if (is_null($this->user)) { $this->user = $user; } if (is_null($this->password)) { $this->password = $password; } if (is_null($this->_database)) { $this->_database = $database; } if (is_null($this->selectDatabase)) { $this->selectDatabase = $select; } // Open the connection if (!is_resource($this->connection) || is_null($this->connection)) { $this->open(); } } /** * 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('mysql_connect')); } /** * Test to see if the MySQL connector is available. * * @return boolean True on success, false otherwise. */ public static function test() { return (function_exists('mysql_connect')); } public function close() { $return = false; if (is_resource($this->cursor)) { mysql_free_result($this->cursor); } if (is_resource($this->connection) || (!is_null($this->connection) && !is_bool($this->connection))) { $return = mysql_close($this->connection); } $this->connection = null; return $return; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. */ public function connected() { if (is_resource($this->connection)) { return @mysql_ping($this->connection); } return false; } /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return Mysql Returns this object to support chaining. */ public function dropTable($table, $ifExists = true) { $query = $this->getQuery(true); $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($table)); $this->query(); return $this; } /** * 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. */ public function escape($text, $extra = false) { if (is_null($text)) { return 'NULL'; } $result = @mysql_real_escape_string($text, $this->getConnection()); if ($result === false) { // Attempt to reconnect. try { $this->connection = null; $this->open(); $result = @mysql_real_escape_string($text, $this->getConnection()); } catch (RuntimeException $e) { $result = $this->unsafe_escape($text); } } if ($extra) { $result = addcslashes($result, '%_'); } return $result; } /** * 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. */ public function fetchAssoc($cursor = null) { return mysql_fetch_assoc($cursor ?: $this->cursor); } /** * 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 */ public function freeResult($cursor = null) { mysql_free_result($cursor ?: $this->cursor); } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. */ public function getAffectedRows() { return mysql_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. */ public function getCollation() { $this->setQuery('SHOW FULL COLUMNS FROM #__ak_stats'); $array = $this->loadAssocList(); return $array['2']['Collation']; } /** * 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. */ public function getNumRows($cursor = null) { return mysql_num_rows($cursor ?: $this->cursor); } /** * Get the current or query, or new JDatabaseQuery object. * * @param boolean $new False to return the last query set, True to return a new JDatabaseQuery object. * * @return mixed The current value of the internal SQL variable or a new JDatabaseQuery object. */ public function getQuery($new = false) { if ($new) { return new QueryMysql($this); } else { return $this->sql; } } public function createQuery() { return new QueryMysql($this); } /** * 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. */ public function getTableColumns($table, $typeOnly = true) { $result = []; // 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; } /** * 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. */ public function getTableCreate($tables) { // Initialise variables. $result = []; // Sanitize input to an array and iterate over the list. $tables = (array) $tables; 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; } /** * Get the details list of keys for a table. * * @param string $tables The name of the table. * * @return array An array of the column specification for the table. */ public function getTableKeys($tables) { // Get the details columns information. $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($tables)); $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. */ public function getTableList() { // Set the query to get the tables statement. $this->setQuery('SHOW TABLES'); $tables = $this->loadColumn(); return $tables; } /** * Returns an array with the names of tables, views, procedures, functions and triggers * in the database. The table names are the keys of the tables, whereas the value is * the type of each element: table, view, merge, temp, procedure, function or trigger. * Note that merge are MRG_MYISAM tables and temp is non-permanent data table, usually * set up as temporary, black hole or federated tables. These two types should never, * ever, have their data dumped in the SQL dump file. * * @param bool $abstract Return abstract or normal names? Defaults to true (abstract names) * * @return array */ public function getTables($abstract = true) { if (!empty($this->tablesCache[$this->_database])) { return $this->tablesCache[$this->_database]; } $sql = "SHOW TABLES"; $this->setQuery($sql); $all_tables = $this->loadColumn(); if (!empty($all_tables)) { // Start by adding tables and views to the list foreach ($all_tables as $table_name) { if ($abstract) { $table_name = $this->getAbstract($table_name); } $this->tablesCache[$this->_database][$table_name] = 'table'; } // Loop all metadatas foreach ($all_tables as $table_metadata) { $table_name = $table_metadata; $table_abstract = $this->getAbstract($table_metadata); $type = 'table'; if ($abstract) { $table_metadata = $table_abstract; } $create = $this->get_create($table_abstract, $table_name, $type); // Scan for the table engine. $engine = null; // So that we detect VIEWs correctly if ($type == 'table') { $engine = 'MyISAM'; // So that even with MySQL 4 hosts we don't screw this up $engine_keys = ['ENGINE=', 'TYPE=']; foreach ($engine_keys as $engine_key) { $start_pos = strrpos($create, $engine_key); if ($start_pos !== false) { // Advance the start position just after the position of the ENGINE keyword $start_pos += strlen($engine_key); // Try to locate the space after the engine type $end_pos = stripos($create, ' ', $start_pos); if ($end_pos === false) { // Uh... maybe it ends with ENGINE=EngineType; $end_pos = stripos($create, ';'); } if ($end_pos !== '') { // Grab the string $engine = substr($create, $start_pos, $end_pos - $start_pos); } } } $engine = strtoupper($engine); } switch ($engine) { // Views -- FIX: They are detected based on their CREATE STATEMENT case null: $this->tablesCache[$this->_database][$table_metadata] = 'view'; break; // Merge tables case 'MRG_MYISAM': $this->tablesCache[$this->_database][$table_metadata] = 'merge'; break; // Tables whose data we do not back up (memory, federated and can-have-no-data tables) case 'MEMORY': case 'EXAMPLE': case 'BLACKHOLE': case 'FEDERATED': $this->tablesCache[$this->_database][$table_metadata] = 'temp'; break; // Normal tables default: break; } // switch } // foreach } // if !empty // If we have MySQL > 5.0 add the list of stored procedures, stored functions // and triggers $registry = Factory::getConfiguration(); $enable_entities = $registry->get('engine.dump.native.advanced_entitites', true); if ($enable_entities) { // 1. Stored procedures $sql = "SHOW PROCEDURE STATUS WHERE " . $this->quoteName('Db') . "=" . $this->quote($this->_database); $this->setQuery($sql); try { $all_entries = $this->loadAssocList(); } catch (Exception $e) { $all_entries = []; } if (is_array($all_entries) || $all_entries instanceof \Countable ? count($all_entries) : 0) { foreach ($all_entries as $entry) { $table_name = $entry['Name']; if ($abstract) { $table_name = $this->getAbstract($table_name); } $this->tablesCache[$this->_database][$table_name] = 'procedure'; } } // 2. Stored functions $sql = "SHOW FUNCTION STATUS WHERE " . $this->quoteName('Db') . "=" . $this->quote($this->_database); $this->setQuery($sql); try { $all_entries = $this->loadColumn(1); } catch (Exception $e) { $all_entries = []; } // If we have filters, make sure the tables pass the filtering if (is_array($all_entries)) { if (count($all_entries)) { foreach ($all_entries as $table_name) { if ($abstract) { $table_name = $this->getAbstract($table_name); } $this->tablesCache[$this->_database][$table_name] = 'function'; } } } // 3. Triggers $sql = "SHOW TRIGGERS"; $this->setQuery($sql); try { $all_entries = $this->loadColumn(); } catch (Exception $e) { $all_entries = []; } // If we have filters, make sure the tables pass the filtering if (is_array($all_entries)) { if (count($all_entries)) { foreach ($all_entries as $table_name) { if ($abstract) { $table_name = $this->getAbstract($table_name); } $this->tablesCache[$this->_database][$table_name] = 'trigger'; } } } } return $this->tablesCache[$this->_database]; } /** * Get the version of the database connector. * * @return string The database connector version. */ public function getVersion() { return mysql_get_server_info($this->connection); } /** * Determines if the database engine supports UTF-8 character encoding. * * @return boolean True if supported. */ public function hasUTF() { $verParts = explode('.', $this->getVersion()); return ($verParts[0] == 5 || ($verParts[0] == 4 && $verParts[1] == 1 && (int) $verParts[2] >= 2)); } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. */ public function insertid() { return mysql_insert_id($this->connection); } /** * Locks a table in the database. * * @param string $tableName The name of the table to unlock. * * @return Mysql Returns this object to support chaining. */ public function lockTable($tableName) { $this->setQuery('LOCK TABLES ' . $this->quoteName($tableName) . ' WRITE')->query(); return $this; } public function open() { if ($this->connected()) { return; } else { $this->close(); } // perform a number of fatality checks, then return gracefully if (!function_exists('mysql_connect')) { $this->errorNum = 1; $this->errorMsg = 'The MySQL adapter "mysql" is not available.'; return; } if (!($this->connection = @mysql_connect($this->host, $this->user, $this->password, true))) { $this->errorNum = 2; $this->errorMsg = 'Could not connect to MySQL'; return; } // Set sql_mode to non_strict mode mysql_query("SET @@SESSION.sql_mode = '';", $this->connection); // If auto-select is enabled select the given database. if ($this->selectDatabase && !empty($this->_database)) { if (!$this->select($this->_database)) { $this->errorNum = 3; $this->errorMsg = "Cannot select database {$this->_database}"; return; } } $this->setUTF(); } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. */ public function query() { if (!is_resource($this->connection)) { 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->limit > 0 || $this->offset > 0) { $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit; } // Increment the query counter. $this->count++; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; } // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. $this->cursor = @mysql_query($query, $this->connection); // If an error occurred handle it. if (!$this->cursor) { // Check if the server was disconnected. if (!$this->connected() && !$this->isReconnecting) { $this->isReconnecting = true; try { // Attempt to reconnect. $this->connection = null; $this->open(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { // Get the error number and message. $this->errorNum = (int) mysql_errno($this->connection); $this->errorMsg = (string) mysql_error($this->connection) . ' SQL=' . $query; // Throw the normal query exception. throw new RuntimeException($this->errorMsg, $this->errorNum); } // Since we were able to reconnect, run the query again. $result = $this->query(); $this->isReconnecting = false; return $result; } // The server was not disconnected. else { // Get the error number and message. $this->errorNum = (int) mysql_errno($this->connection); $this->errorMsg = (string) mysql_error($this->connection) . ' SQL=' . $query; // Throw the normal query exception. if ($this->errorNum != 0) { 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 Mysql Returns this object to support chaining. */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->query(); 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. */ public function select($database) { if (!$database) { return false; } if (!mysql_select_db($database, $this->connection)) { throw new RuntimeException('Could not connect to database'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. */ public function setUTF() { $result = false; if ($this->supportsUtf8mb4()) { $result = @mysql_set_charset('utf8mb4', $this->connection); } if (!$result) { $result = @mysql_set_charset('utf8', $this->connection); } return $result; } /** * Method to commit a transaction. * * @return void */ public function transactionCommit() { $this->setQuery('COMMIT'); $this->execute(); } /** * Method to roll back a transaction. * * @return void */ public function transactionRollback() { $this->setQuery('ROLLBACK'); $this->execute(); } /** * Method to initialize a transaction. * * @return void */ public function transactionStart() { $this->setQuery('START TRANSACTION'); $this->execute(); } /** * Unlocks tables in the database. * * @return Mysql Returns this object to support chaining. * * @throws Exception * @since 11.4 */ public function unlockTables() { $this->setQuery('UNLOCK TABLES')->execute(); return $this; } /** * 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. */ protected function fetchArray($cursor = null) { return mysql_fetch_row($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. */ protected function fetchObject($cursor = null, $class = 'stdClass') { return mysql_fetch_object($cursor ?: $this->cursor, $class); } /** * Gets the CREATE TABLE command for a given table/view * * @param string $table_abstract The abstracted name of the entity * @param string $table_name The name of the table * @param string $type The type of the entity to scan. If it's found to differ, the correct type is * returned. * * @return string The CREATE command, w/out newlines */ protected function get_create($table_abstract, $table_name, &$type) { $sql = "SHOW CREATE TABLE `$table_abstract`"; $this->setQuery($sql); $temp = $this->loadRowList(); $table_sql = $temp[0][1]; unset($temp); // Smart table type detection if (in_array($type, ['table', 'merge', 'view'])) { // Check for CREATE VIEW $pattern = '/^CREATE(.*) VIEW (.*)/i'; $result = preg_match($pattern, $table_sql); if ($result === 1) { // This is a view. $type = 'view'; } else { // This is a table. $type = 'table'; } // Is it a VIEW but we don't have SHOW VIEW privileges? if (empty($table_sql)) { $type = 'view'; } } $table_sql = str_replace($table_name, $table_abstract, $table_sql); // Replace newlines with spaces $table_sql = str_replace("\n", " ", $table_sql) . ";\n"; $table_sql = str_replace("\r", " ", $table_sql); $table_sql = str_replace("\t", " ", $table_sql); // Post-process CREATE VIEW if ($type == 'view') { $pos_view = strpos($table_sql, ' VIEW '); if ($pos_view > 7) { // Only post process if there are view properties between the CREATE and VIEW keywords $propstring = substr($table_sql, 7, $pos_view - 7); // Properties string // Fetch the ALGORITHM={UNDEFINED | MERGE | TEMPTABLE} keyword $algostring = ''; $algo_start = strpos($propstring, 'ALGORITHM='); if ($algo_start !== false) { $algo_end = strpos($propstring, ' ', $algo_start); $algostring = substr($propstring, $algo_start, $algo_end - $algo_start + 1); } // Create our modified create statement $table_sql = 'CREATE OR REPLACE ' . $algostring . substr($table_sql, $pos_view); } } return $table_sql; } /** * Does this database server support UTF-8 four byte (utf8mb4) collation? * * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9. * * This method's code is based on WordPress' wpdb::has_cap() method * * @return bool */ protected function supportsUtf8mb4() { $client_version = mysql_get_client_info(); if (strpos($client_version, 'mysqlnd') !== false) { $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version); return version_compare($client_version, '5.0.9', '>='); } else { return version_compare($client_version, '5.5.3', '>='); } } protected function unsafe_escape($string) { if (function_exists('mb_ereg_replace')) { return mb_ereg_replace('[\x00\x0A\x0D\x1A\x22\x27\x5C]', '\\\0', $string); } return preg_replace('~[\x00\x0A\x0D\x1A\x22\x27\x5C]~u', '\\\$0', $string); } } Driver/Mysqli.php 0000604 00000036424 15245670452 0010005 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Mysqli as QueryMysqli; use Akeeba\Engine\FixMySQLHostname; use mysqli_result; use RuntimeException; /** * MySQL Improved (mysqli) database driver for Akeeba Engine * * Based on Joomla! Platform 11.2 */ #[\AllowDynamicProperties] class Mysqli extends Mysql { use FixMySQLHostname; /** * The name of the database driver. * * @var string * @since 11.1 */ public $name = 'mysqli'; /** @var \mysqli|null The db connection resource */ protected $connection = ''; /** @var mysqli_result|null The database connection cursor from the last query. */ protected $cursor; protected $port; protected $socket; protected $ssl = []; /** @var bool Are we in the process of reconnecting to the database server? */ private $isReconnecting = false; /** * Database object constructor * * @param array $options List of options used to configure the connection */ public function __construct($options) { $this->driverType = 'mysql'; // Init $this->nameQuote = '`'; $options['ssl'] = $options['ssl'] ?? []; $options['ssl'] = is_array($options['ssl']) ? $options['ssl'] : []; $options['ssl']['enable'] = ($options['ssl']['enable'] ?? $options['dbencryption'] ?? false) ?: false; $options['ssl']['cipher'] = ($options['ssl']['cipher'] ?? $options['dbsslcipher'] ?? null) ?: null; $options['ssl']['ca'] = ($options['ssl']['ca'] ?? $options['dbsslca'] ?? null) ?: null; $options['ssl']['capath'] = ($options['ssl']['capath'] ?? $options['dbsslcapath'] ?? null) ?: null; $options['ssl']['key'] = ($options['ssl']['key'] ?? $options['dbsslkey'] ?? null) ?: null; $options['ssl']['cert'] = ($options['ssl']['cert'] ?? $options['dbsslcert'] ?? null) ?: null; $options['ssl']['verify_server_cert'] = ($options['ssl']['verify_server_cert'] ?? $options['dbsslverifyservercert'] ?? false) ?: false; // Figure out if a port is included in the host name $this->fixHostnamePortSocket($options['host'], $options['port'], $options['socket']); // Set the information $this->host = $options['host'] ?? 'localhost'; $this->user = $options['user'] ?? ''; $this->password = $options['password'] ?? ''; $this->port = $options['port'] ?? ''; $this->socket = $options['socket'] ?? ''; $this->_database = $options['database'] ?? ''; $this->selectDatabase = $options['select'] ?? true; $this->ssl = $options['ssl'] ?? []; // Finalize initialization. Also opens the connection. parent::__construct($options); } /** * Test to see if the MySQL connector is available. * * @return boolean True on success, false otherwise. */ public static function isSupported() { return (function_exists('mysqli_connect')); } public function close() { $return = false; if (is_object($this->cursor) && ($this->cursor instanceof mysqli_result)) { try { @$this->cursor->free(); } catch (\Throwable $e) { } $this->cursor = null; } if (is_object($this->connection) && ($this->connection instanceof \mysqli)) { try { $return = @$this->connection->close(); } catch (\Throwable $e) { $return = false; } } $this->connection = null; return $return; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. */ public function connected() { if (!is_object($this->connection)) { return false; } // mysqli_ping is deprecated since PHP 8.4. if (version_compare(PHP_VERSION, '8.4.0', '<')) { try { return @mysqli_ping($this->connection); } catch (\Throwable $e) { return false; } } try { $cursor = @mysqli_query($this->connection, 'SELECT 1'); if (!$cursor) { return false; } mysqli_free_result($cursor); return true; } catch (\Throwable $e) { return false; } } /** * 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. */ public function escape($text, $extra = false) { if (is_null($text)) { return 'NULL'; } $result = @mysqli_real_escape_string($this->getConnection(), $text); if ($result === false) { // Attempt to reconnect. try { $this->connection = null; $this->open(); $result = @mysqli_real_escape_string($this->getConnection(), $text);; } catch (RuntimeException $e) { $result = $this->unsafe_escape($text); } } if ($extra) { $result = addcslashes($result, '%_'); } return $result; } /** * 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. */ public function fetchAssoc($cursor = null) { return mysqli_fetch_assoc($cursor ?: $this->cursor); } /** * 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 */ public function freeResult($cursor = null) { mysqli_free_result($cursor ?: $this->cursor); } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. */ public function getAffectedRows() { return mysqli_affected_rows($this->connection); } /** * Get the number of returned rows for the previous executed SQL statement. * * @param mysqli_result $cursor An optional database cursor resource to extract the row count from. * * @return integer The number of returned rows. */ public function getNumRows($cursor = null) { return mysqli_num_rows($cursor ?: $this->cursor); } /** * Get the current or query, or new JDatabaseQuery object. * * @param boolean $new False to return the last query set, True to return a new JDatabaseQuery object. * * @return mixed The current value of the internal SQL variable or a new JDatabaseQuery object. */ public function getQuery($new = false) { if ($new) { return new QueryMysqli($this); } else { return $this->sql; } } public function createQuery() { return new QueryMysqli($this); } /** * Get the version of the database connector. * * @return string The database connector version. */ public function getVersion() { return mysqli_get_server_info($this->connection); } /** * Determines if the database engine supports UTF-8 character encoding. * * @return boolean True if supported. */ public function hasUTF() { $mariadb = stripos($this->connection->server_info, 'mariadb') !== false; $client_version = mysqli_get_client_info(); $server_version = $this->getVersion(); if (version_compare($server_version, '5.5.3', '<')) { return false; } if ($mariadb && version_compare($server_version, '10.0.0', '<')) { return false; } if (strpos($client_version, 'mysqlnd') !== false) { $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version); return version_compare($client_version, '5.0.9', '>='); } return version_compare($client_version, '5.5.3', '>='); } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. */ public function insertid() { return mysqli_insert_id($this->connection); } public function open() { if ($this->connected()) { return; } else { $this->close(); } // perform a number of fatality checks, then return gracefully if (!function_exists('mysqli_connect')) { $this->errorNum = 1; $this->errorMsg = 'The MySQL adapter "mysqli" is not available.'; return; } // Let's prepare a connection $this->connection = mysqli_init(); $connectionFlags = 0; // For SSL/TLS connection encryption. if ($this->ssl !== [] && $this->ssl['enable'] === true) { $connectionFlags = $connectionFlags | MYSQLI_CLIENT_SSL; // Verify server certificate is only available in PHP 5.6.16+. See https://www.php.net/ChangeLog-5.php#5.6.16 if (isset($this->ssl['verify_server_cert'])) { // New constants in PHP 5.6.16+. See https://www.php.net/ChangeLog-5.php#5.6.16 if ($this->ssl['verify_server_cert'] === true && defined('MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT')) { $connectionFlags = $connectionFlags | MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT; } elseif ($this->ssl['verify_server_cert'] === false && defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')) { $connectionFlags = $connectionFlags | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } elseif (defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT')) { $this->connection->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, $this->ssl['verify_server_cert']); } } // Add SSL/TLS options only if changed. $this->connection->ssl_set( ($this->ssl['key'] ?? null) ?: null, ($this->ssl['cert'] ?? null) ?: null, ($this->ssl['ca'] ?? null) ?: null, ($this->ssl['capath'] ?? null) ?: null, ($this->ssl['cipher'] ?? null) ?: null ); } // Attempt to connect to the server, use error suppression to silence warnings and allow us to throw an Exception separately. try { $connected = @$this->connection->real_connect( $this->host, $this->user, $this->password ?: null, null, $this->port ?: 3306, $this->socket ?: null, $connectionFlags ); } catch (\Throwable $e) { $connected = false; } // connect to the server if (!$connected) { $this->errorNum = 2; $this->errorMsg = 'Could not connect to MySQL'; return; } // Set sql_mode to non_strict mode mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';"); if ($this->selectDatabase && !empty($this->_database)) { if (!$this->select($this->_database)) { $this->errorNum = 3; $this->errorMsg = "Cannot select database {$this->_database}"; return; } } $this->setUTF(); } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. */ public function query() { $this->open(); if (!is_object($this->connection)) { 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->limit > 0 || $this->offset > 0) { $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit; } // Increment the query counter. $this->count++; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; } // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // 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 an error occurred handle it. if (!$this->cursor) { $this->errorNum = 0; $this->errorMsg = ''; if ($this->connection) { $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() && !$this->isReconnecting) { $this->isReconnecting = true; try { // Attempt to reconnect. $this->connection = null; $this->open(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { throw new RuntimeException($this->errorMsg, $this->errorNum); } // Since we were able to reconnect, run the query again. $result = $this->query(); $this->isReconnecting = false; return $result; } // The server was not disconnected. elseif ($this->errorNum != 0) { throw new RuntimeException($this->errorMsg, $this->errorNum); } } return $this->cursor; } /** * 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. */ public function select($database) { if (!$database) { return false; } if (!mysqli_select_db($this->connection, $database)) { return false; } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. */ public function setUTF() { $result = false; if ($this->supportsUtf8mb4()) { $result = @mysqli_set_charset($this->connection, 'utf8mb4'); } if (!$result) { $result = @mysqli_set_charset($this->connection, 'utf8'); } return $result; } /** * Does this database server support UTF-8 four byte (utf8mb4) collation? * * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9. * * This method's code is based on WordPress' wpdb::has_cap() method * * @return bool */ public function supportsUtf8mb4() { $client_version = mysqli_get_client_info(); if (strpos($client_version, 'mysqlnd') !== false) { $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version); return version_compare($client_version, '5.0.9', '>='); } else { return version_compare($client_version, '5.5.3', '>='); } } /** * 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. */ protected function fetchArray($cursor = null) { return mysqli_fetch_row($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. */ protected function fetchObject($cursor = null, $class = 'stdClass') { return mysqli_fetch_object($cursor ?: $this->cursor, $class); } } Driver/QueryException.php 0000604 00000001657 15245670452 0011513 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Exception; class QueryException extends Exception { } Driver/Query/Base.php 0000604 00000113371 15245670452 0010503 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Base as DriverBase; use Akeeba\Engine\Driver\Query\Element as QueryElement; use Akeeba\Engine\Driver\Query\Limitable as QueryLimitable; use Akeeba\Engine\Driver\QueryException; /** * Query Building Class. * * Based on Joomla! Platform 11.3 */ abstract class Base { /** * @var DriverBase The database connection resource. */ protected $db = null; /** * @var string The SQL query (if a direct query string was provided). */ protected $sql = null; /** * @var string The query type. */ protected $type = ''; /** * @var QueryElement The query element for a generic query (type = null). */ protected $element = null; /** * @var QueryElement The select element. */ protected $select = null; /** * @var QueryElement The delete element. */ protected $delete = null; /** * @var QueryElement The update element. */ protected $update = null; /** * @var QueryElement The insert element. */ protected $insert = null; /** * @var QueryElement The from element. */ protected $from = null; /** * @var QueryElement The join element. */ protected $join = null; /** * @var QueryElement The set element. */ protected $set = null; /** * @var QueryElement The where element. */ protected $where = null; /** * @var QueryElement The group by element. */ protected $group = null; /** * @var QueryElement The having element. */ protected $having = null; /** * @var QueryElement The column list for an INSERT statement. */ protected $columns = null; /** * @var QueryElement The values list for an INSERT statement. */ protected $values = null; /** * @var QueryElement The order element. */ protected $order = null; /** * @var object The auto increment insert field element. */ protected $autoIncrementField = null; /** * @var QueryElement The call element. */ protected $call = null; /** * @var QueryElement The exec element. */ protected $exec = null; /** * @var QueryElement The union element. */ protected $union = null; /** * @var QueryElement The unionAll element. */ protected $unionAll = null; /** * Class constructor. * * @param DriverBase $db The database connector resource. */ public function __construct(DriverBase $db = null) { $this->db = $db; } /** * Magic method to provide method alias support for quote() and quoteName(). * * @param string $method The called method. * @param array $args The array of arguments passed to the method. * * @return string The aliased method's return value or null. */ public function __call($method, $args) { if (empty($args)) { return null; } switch ($method) { case 'q': return $this->quote($args[0], $args[1] ?? true); break; case 'qn': return $this->quoteName($args[0]); break; case 'e': return $this->escape($args[0], $args[1] ?? false); break; } return null; } /** * Magic function to convert the query to a string. * * @return string The completed query. */ public function __toString() { $query = ''; if ($this->sql) { return $this->sql; } switch ($this->type) { case 'element': $query .= (string) $this->element; break; case 'select': $query .= (string) $this->select; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->group) { $query .= (string) $this->group; } if ($this->having) { $query .= (string) $this->having; } if ($this->order) { $query .= (string) $this->order; } break; case 'union': $query .= (string) $this->union; break; case 'unionAll': $query .= (string) $this->unionAll; break; case 'delete': $query .= (string) $this->delete; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } break; case 'update': $query .= (string) $this->update; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } $query .= (string) $this->set; if ($this->where) { $query .= (string) $this->where; } break; case 'insert': $query .= (string) $this->insert; // Set method if ($this->set) { $query .= (string) $this->set; } // Columns-Values method elseif ($this->values) { if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->values->getElements(); if (!($elements[0] instanceof $this)) { $query .= ' VALUES '; } $query .= (string) $this->values; } break; case 'call': $query .= (string) $this->call; break; case 'exec': $query .= (string) $this->exec; break; } if ($this instanceof QueryLimitable) { $query = $this->processLimit($query, $this->limit, $this->offset); } return $query; } /** * Magic function to get protected variable value * * @param string $name The name of the variable. * * @return mixed */ public function __get($name) { return $this->$name ?? null; } /** * Add a single column, or array of columns to the CALL clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * The call method can, however, be called multiple times in the same query. * * Usage: * $query->call('a.*')->call('b.id'); * $query->call(array('a.*', 'b.id')); * * @param mixed $columns A string or an array of field names. * * @return Base Returns this object to allow chaining. */ public function call($columns) { $this->type = 'call'; if (is_null($this->call)) { $this->call = new QueryElement('CALL', $columns); } else { $this->call->append($columns); } return $this; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * Usage: * $query->select($query->castAsChar('a')); * * @param string $value The value to cast as a char. * * @return string Returns the cast value. */ public function castAsChar($value) { return $value; } /** * Gets the number of characters in a string. * * Note, use 'length' to find the number of bytes in a string. * * Usage: * $query->select($query->charLength('a')); * * @param string $field A value. * @param string $operator Comparison operator between charLength integer value and $condition * @param string $condition Integer value to compare charLength with. * * @return string The required char length call. */ public function charLength($field, $operator = null, $condition = null) { return 'CHAR_LENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : ''); } /** * Clear data from the query or a specific clause of the query. * * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query. * * @return Base Returns this object to allow chaining. */ public function clear($clause = null) { $this->sql = null; switch ($clause) { case 'select': $this->select = null; $this->type = null; break; case 'delete': $this->delete = null; $this->type = null; break; case 'update': $this->update = null; $this->type = null; break; case 'insert': $this->insert = null; $this->type = null; $this->autoIncrementField = null; break; case 'from': $this->from = null; break; case 'join': $this->join = null; break; case 'set': $this->set = null; break; case 'where': $this->where = null; break; case 'group': $this->group = null; break; case 'having': $this->having = null; break; case 'order': $this->order = null; break; case 'columns': $this->columns = null; break; case 'values': $this->values = null; break; case 'exec': $this->exec = null; $this->type = null; break; case 'call': $this->call = null; $this->type = null; break; case 'limit': $this->offset = 0; $this->limit = 0; break; case 'union': $this->union = null; break; case 'unionAll': $this->unionAll = null; break; default: $this->type = null; $this->select = null; $this->delete = null; $this->update = null; $this->insert = null; $this->from = null; $this->join = null; $this->set = null; $this->where = null; $this->group = null; $this->having = null; $this->order = null; $this->columns = null; $this->values = null; $this->autoIncrementField = null; $this->exec = null; $this->call = null; $this->union = null; $this->unionAll = null; $this->offset = 0; $this->limit = 0; break; } return $this; } /** * Adds a column, or array of column names that would be used for an INSERT INTO statement. * * @param mixed $columns A column name, or array of column names. * * @return Base Returns this object to allow chaining. */ public function columns($columns) { if (is_null($this->columns)) { $this->columns = new QueryElement('()', $columns); } else { $this->columns->append($columns); } return $this; } /** * Concatenates an array of column names or values. * * Usage: * $query->select($query->concatenate(array('a', 'b'))); * * @param array $values An array of values to concatenate. * @param string $separator As separator to place between each value. * * @return string The concatenated values. */ public function concatenate($values, $separator = null) { if ($separator) { return 'CONCATENATE(' . implode(' || ' . $this->quote($separator) . ' || ', $values) . ')'; } else { return 'CONCATENATE(' . implode(' || ', $values) . ')'; } } /** * Gets the current date and time. * * Usage: * $query->where('published_up < '.$query->currentTimestamp()); * * @return string */ public function currentTimestamp() { return 'CURRENT_TIMESTAMP()'; } /** * Returns a PHP date() function compliant date format for the database driver. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the getDateFormat method directly. * * @return string The format string. */ public function dateFormat() { if (!($this->db instanceof DriverBase)) { throw new QueryException('Invalid database object'); } return $this->db->getDateFormat(); } /** * Creates a formatted dump of the query for debugging purposes. * * Usage: * echo $query->dump(); * * @return string */ public function dump() { return '<pre class="AkeebaEngineQuery">' . str_replace('#__', $this->db->getPrefix(), $this) . '</pre>'; } /** * Add a table name to the DELETE clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * * Usage: * $query->delete('#__a')->where('id = 1'); * * @param string $table The name of the table to delete from. * * @return Base Returns this object to allow chaining. */ public function delete($table = null) { $this->type = 'delete'; $this->delete = new QueryElement('DELETE', null); if (!empty($table)) { $this->from($table); } return $this; } /** * Method to escape a string for usage in an SQL statement. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the escape method directly. * * Note that 'e' is an alias for this method as it is in DriverBase. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. */ public function escape($text, $extra = false) { if (!($this->db instanceof DriverBase)) { throw new QueryException('Invalid database object'); } return $this->db->escape($text, $extra); } /** * Add a single column, or array of columns to the EXEC clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * The exec method can, however, be called multiple times in the same query. * * Usage: * $query->exec('a.*')->exec('b.id'); * $query->exec(array('a.*', 'b.id')); * * @param mixed $columns A string or an array of field names. * * @return Base Returns this object to allow chaining. */ public function exec($columns) { $this->type = 'exec'; if (is_null($this->exec)) { $this->exec = new QueryElement('EXEC', $columns); } else { $this->exec->append($columns); } return $this; } /** * Add a table to the FROM clause of the query. * * Note that while an array of tables can be provided, it is recommended you use explicit joins. * * Usage: * $query->select('*')->from('#__a'); * * @param mixed $tables A string or array of table names. * This can be a Base object (or a child of it) when used * as a subquery in FROM clause along with a value for $subQueryAlias. * @param string $subQueryAlias Alias used when $tables is a Base. * * @return Base Returns this object to allow chaining. */ public function from($tables, $subQueryAlias = null) { if (is_null($this->from)) { if ($tables instanceof $this) { if (is_null($subQueryAlias)) { throw new QueryException('Null subquery defined'); } $tables = '( ' . (string) $tables . ' ) AS ' . $this->quoteName($subQueryAlias); } $this->from = new QueryElement('FROM', $tables); } else { $this->from->append($tables); } return $this; } /** * Used to get a string to extract year from date column. * * Usage: * $query->select($query->year($query->quoteName('dateColumn'))); * * @param string $date Date column containing year to be extracted. * * @return string Returns string to extract year from a date. */ public function year($date) { return 'YEAR(' . $date . ')'; } /** * Used to get a string to extract month from date column. * * Usage: * $query->select($query->month($query->quoteName('dateColumn'))); * * @param string $date Date column containing month to be extracted. * * @return string Returns string to extract month from a date. */ public function month($date) { return 'MONTH(' . $date . ')'; } /** * Used to get a string to extract day from date column. * * Usage: * $query->select($query->day($query->quoteName('dateColumn'))); * * @param string $date Date column containing day to be extracted. * * @return string Returns string to extract day from a date. */ public function day($date) { return 'DAY(' . $date . ')'; } /** * Used to get a string to extract hour from date column. * * Usage: * $query->select($query->hour($query->quoteName('dateColumn'))); * * @param string $date Date column containing hour to be extracted. * * @return string Returns string to extract hour from a date. */ public function hour($date) { return 'HOUR(' . $date . ')'; } /** * Used to get a string to extract minute from date column. * * Usage: * $query->select($query->minute($query->quoteName('dateColumn'))); * * @param string $date Date column containing minute to be extracted. * * @return string Returns string to extract minute from a date. */ public function minute($date) { return 'MINUTE(' . $date . ')'; } /** * Used to get a string to extract seconds from date column. * * Usage: * $query->select($query->second($query->quoteName('dateColumn'))); * * @param string $date Date column containing second to be extracted. * * @return string Returns string to extract second from a date. */ public function second($date) { return 'SECOND(' . $date . ')'; } /** * Add a grouping column to the GROUP clause of the query. * * Usage: * $query->group('id'); * * @param mixed $columns A string or array of ordering columns. * * @return Base Returns this object to allow chaining. */ public function group($columns) { if (is_null($this->group)) { $this->group = new QueryElement('GROUP BY', $columns); } else { $this->group->append($columns); } return $this; } /** * A conditions to the HAVING clause of the query. * * Usage: * $query->group('id')->having('COUNT(id) > 5'); * * @param mixed $conditions A string or array of columns. * @param string $glue The glue by which to join the conditions. Defaults to AND. * * @return Base Returns this object to allow chaining. */ public function having($conditions, $glue = 'AND') { if (is_null($this->having)) { $glue = strtoupper($glue); $this->having = new QueryElement('HAVING', $conditions, " $glue "); } else { $this->having->append($conditions); } return $this; } /** * Add an INNER JOIN clause to the query. * * Usage: * $query->innerJoin('b ON b.id = a.id')->innerJoin('c ON c.id = b.id'); * * @param string $condition The join condition. * * @return Base Returns this object to allow chaining. */ public function innerJoin($condition) { $this->join('INNER', $condition); return $this; } /** * Add a table name to the INSERT clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * * Usage: * $query->insert('#__a')->set('id = 1'); * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4'); * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4')); * * @param mixed $table The name of the table to insert data into. * @param boolean $incrementField The name of the field to auto increment. * * @return Base Returns this object to allow chaining. */ public function insert($table, $incrementField = false) { $this->type = 'insert'; $this->insert = new QueryElement('INSERT INTO', $table); $this->autoIncrementField = $incrementField; return $this; } /** * Add a JOIN clause to the query. * * Usage: * $query->join('INNER', 'b ON b.id = a.id); * * @param string $type The type of join. This string is prepended to the JOIN keyword. * @param string $conditions A string or array of conditions. * * @return Base Returns this object to allow chaining. */ public function join($type, $conditions) { if (is_null($this->join)) { $this->join = []; } $this->join[] = new QueryElement(strtoupper($type) . ' JOIN', $conditions); return $this; } /** * Add a LEFT JOIN clause to the query. * * Usage: * $query->leftJoin('b ON b.id = a.id')->leftJoin('c ON c.id = b.id'); * * @param string $condition The join condition. * * @return Base Returns this object to allow chaining. */ public function leftJoin($condition) { $this->join('LEFT', $condition); return $this; } /** * Get the length of a string in bytes. * * Note, use 'charLength' to find the number of characters in a string. * * Usage: * query->where($query->length('a').' > 3'); * * @param string $value The string to measure. * * @return int */ public function length($value) { return 'LENGTH(' . $value . ')'; } /** * Get the null or zero representation of a timestamp for the database driver. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the nullDate method directly. * * Usage: * $query->where('modified_date <> '.$query->nullDate()); * * @param boolean $quoted Optionally wraps the null date in database quotes (true by default). * * @return string Null or zero representation of a timestamp. */ public function nullDate($quoted = true) { if (!($this->db instanceof DriverBase)) { throw new QueryException('Invalid database object'); } $result = $this->db->getNullDate(); if ($quoted) { return $this->db->quote($result); } return $result; } /** * Add a ordering column to the ORDER clause of the query. * * Usage: * $query->order('foo')->order('bar'); * $query->order(array('foo','bar')); * * @param mixed $columns A string or array of ordering columns. * * @return Base Returns this object to allow chaining. */ public function order($columns) { if (is_null($this->order)) { $this->order = new QueryElement('ORDER BY', $columns); } else { $this->order->append($columns); } return $this; } /** * Add an OUTER JOIN clause to the query. * * Usage: * $query->outerJoin('b ON b.id = a.id')->outerJoin('c ON c.id = b.id'); * * @param string $condition The join condition. * * @return Base Returns this object to allow chaining. */ public function outerJoin($condition) { $this->join('OUTER', $condition); return $this; } /** * Method to quote and optionally escape a string to database requirements for insertion into the database. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the quote method directly. * * Note that 'q' is an alias for this method as it is in DriverBase. * * Usage: * $query->quote('fulltext'); * $query->q('fulltext'); * $query->q(array('option', 'fulltext')); * * @param mixed $text A string or an array of strings to quote. * @param boolean $escape True to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @throws QueryException if the internal db property is not a valid object. */ public function quote($text, $escape = true) { if (!($this->db instanceof DriverBase)) { throw new QueryException('Invalid database object'); } return $this->db->quote($text, $escape); } /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. * * This method is provided for use where the query object is passed to a function for modification. * If you have direct access to the database object, it is recommended you use the quoteName method directly. * * Note that 'qn' is an alias for this method as it is in DriverBase. * * Usage: * $query->quoteName('#__a'); * $query->qn('#__a'); * * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. * Each type supports dot-notation name. * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has * to be same length of $name; if is null there will not be any AS part for string or array * element. * * @return mixed The quote wrapped name, same type of $name. * * @throws QueryException if the internal db property is not a valid object. */ public function quoteName($name, $as = null) { if (!($this->db instanceof DriverBase)) { throw new QueryException('Invalid database object'); } return $this->db->quoteName($name, $as); } /** * Add a RIGHT JOIN clause to the query. * * Usage: * $query->rightJoin('b ON b.id = a.id')->rightJoin('c ON c.id = b.id'); * * @param string $condition The join condition. * * @return Base Returns this object to allow chaining. */ public function rightJoin($condition) { $this->join('RIGHT', $condition); return $this; } /** * Add a single column, or array of columns to the SELECT clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * The select method can, however, be called multiple times in the same query. * * Usage: * $query->select('a.*')->select('b.id'); * $query->select(array('a.*', 'b.id')); * * @param mixed $columns A string or an array of field names. * * @return Base Returns this object to allow chaining. */ public function select($columns) { $this->type = 'select'; if (is_null($this->select)) { $this->select = new QueryElement('SELECT', $columns); } else { $this->select->append($columns); } return $this; } /** * Add a single condition string, or an array of strings to the SET clause of the query. * * Usage: * $query->set('a = 1')->set('b = 2'); * $query->set(array('a = 1', 'b = 2'); * * @param mixed $conditions A string or array of string conditions. * @param string $glue The glue by which to join the condition strings. Defaults to ,. * Note that the glue is set on first use and cannot be changed. * * @return Base Returns this object to allow chaining. */ public function set($conditions, $glue = ',') { if (is_null($this->set)) { $glue = strtoupper($glue); $this->set = new QueryElement('SET', $conditions, "\n\t$glue "); } else { $this->set->append($conditions); } return $this; } /** * Allows a direct query to be provided to the database * driver's setQuery() method, but still allow queries * to have bounded variables. * * Usage: * $query->setQuery('select * from #__users'); * * @param mixed $query An SQL Query * * @return QueryElement Returns this object to allow chaining. */ public function setQuery($query) { $this->sql = $query; return $this; } /** * Add a table name to the UPDATE clause of the query. * * Note that you must not mix insert, update, delete and select method calls when building a query. * * Usage: * $query->update('#__foo')->set(...); * * @param string $table A table to update. * * @return Base Returns this object to allow chaining. */ public function update($table) { $this->type = 'update'; $this->update = new QueryElement('UPDATE', $table); return $this; } /** * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement. * * Usage: * $query->values('1,2,3')->values('4,5,6'); * $query->values(array('1,2,3', '4,5,6')); * * @param string $values A single tuple, or array of tuples. * * @return Base Returns this object to allow chaining. */ public function values($values) { if (is_null($this->values)) { $this->values = new QueryElement('()', $values, '),('); } else { $this->values->append($values); } return $this; } /** * Add a single condition, or an array of conditions to the WHERE clause of the query. * * Usage: * $query->where('a = 1')->where('b = 2'); * $query->where(array('a = 1', 'b = 2')); * * @param mixed $conditions A string or array of where conditions. * @param string $glue The glue by which to join the conditions. Defaults to AND. * Note that the glue is set on first use and cannot be changed. * * @return Base Returns this object to allow chaining. */ public function where($conditions, $glue = 'AND') { if (is_null($this->where)) { $glue = strtoupper($glue); $this->where = new QueryElement('WHERE', $conditions, " $glue "); } else { $this->where->append($conditions); } return $this; } /** * Method to provide deep copy support to nested objects and * arrays when cloning. * * @return void */ public function __clone() { foreach ($this as $k => $v) { if ($k === 'db') { continue; } if (is_object($v) || is_array($v)) { $this->$k = unserialize(serialize($v)); } } } /** * Add a query to UNION with the current query. * Multiple unions each require separate statements and create an array of unions. * * Usage: * $query->union('SELECT name FROM #__foo') * $query->union('SELECT name FROM #__foo','distinct') * $query->union(array('SELECT name FROM #__foo','SELECT name FROM #__bar')) * * @param mixed $query The Base object or string to union. * @param boolean $distinct True to only return distinct rows from the union. * @param string $glue The glue by which to join the conditions. * * @return mixed The Base object on success or boolean false on failure. */ public function union($query, $distinct = false, $glue = '') { // Clear any ORDER BY clause in UNION query // See http://dev.mysql.com/doc/refman/5.0/en/union.html if (!is_null($this->order)) { $this->clear('order'); } // Set up the DISTINCT flag, the name with parentheses, and the glue. if ($distinct) { $name = 'UNION DISTINCT ()'; $glue = ')' . PHP_EOL . 'UNION DISTINCT ('; } else { $glue = ')' . PHP_EOL . 'UNION ('; $name = 'UNION ()'; } // Get the QueryElement if it does not exist if (is_null($this->union)) { $this->union = new QueryElement($name, $query, "$glue"); } // Otherwise append the second UNION. else { $glue = ''; $this->union->append($query); } return $this; } /** * Add a query to UNION DISTINCT with the current query. Simply a proxy to Union with the Distinct clause. * * Usage: * $query->unionDistinct('SELECT name FROM #__foo') * * @param mixed $query The Base object or string to union. * @param string $glue The glue by which to join the conditions. * * @return mixed The Base object on success or boolean false on failure. */ public function unionDistinct($query, $glue = '') { $distinct = true; // Apply the distinct flag to the union. return $this->union($query, $distinct, $glue); } /** * Find and replace sprintf-like tokens in a format string. * Each token takes one of the following forms: * %% - A literal percent character. * %[t] - Where [t] is a type specifier. * %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier. * * Types: * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped. * e - Escape: Replacement text is passed to $this->escape(). * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument. * n - Name Quote: Replacement text is passed to $this->quoteName(). * q - Quote: Replacement text is passed to $this->quote(). * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument. * r - Raw: Replacement text is used as-is. (Be careful) * * Date Types: * - Replacement text automatically quoted (use uppercase for Name Quote). * - Replacement text should be a string in date format or name of a date column. * y/Y - Year * m/M - Month * d/D - Day * h/H - Hour * i/I - Minute * s/S - Second * * Invariable Types: * - Takes no argument. * - Argument index not incremented. * t - Replacement text is the result of $this->currentTimestamp(). * z - Replacement text is the result of $this->nullDate(false). * Z - Replacement text is the result of $this->nullDate(true). * * Usage: * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1); * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1 * * Notes: * The argument specifier is optional but recommended for clarity. * The argument index used for unspecified tokens is incremented only when used. * * @param string $format The formatting string. * * @return string Returns a string produced according to the formatting string. */ public function format($format) { $query = $this; $args = array_slice(func_get_args(), 1); array_unshift($args, null); $i = 1; $func = function ($match) use ($query, $args, &$i) { if (isset($match[6]) && $match[6] == '%') { return '%'; } // No argument required, do not increment the argument index. switch ($match[5]) { case 't': return $query->currentTimestamp(); break; case 'z': return $query->nullDate(false); break; case 'Z': return $query->nullDate(true); break; } // Increment the argument index only if argument specifier not provided. $index = is_numeric($match[4]) ? (int) $match[4] : $i++; if (!$index || !isset($args[$index])) { $replacement = ''; } else { $replacement = $args[$index]; } switch ($match[5]) { case 'a': return 0 + $replacement; break; case 'e': return $query->escape($replacement); break; case 'E': return $query->escape($replacement, true); break; case 'n': return $query->quoteName($replacement); break; case 'q': return $query->quote($replacement); break; case 'Q': return $query->quote($replacement, false); break; case 'r': return $replacement; break; // Dates case 'y': return $query->year($query->quote($replacement)); break; case 'Y': return $query->year($query->quoteName($replacement)); break; case 'm': return $query->month($query->quote($replacement)); break; case 'M': return $query->month($query->quoteName($replacement)); break; case 'd': return $query->day($query->quote($replacement)); break; case 'D': return $query->day($query->quoteName($replacement)); break; case 'h': return $query->hour($query->quote($replacement)); break; case 'H': return $query->hour($query->quoteName($replacement)); break; case 'i': return $query->minute($query->quote($replacement)); break; case 'I': return $query->minute($query->quoteName($replacement)); break; case 's': return $query->second($query->quote($replacement)); break; case 'S': return $query->second($query->quoteName($replacement)); break; } return ''; }; /** * Regexp to find an replace all tokens. * Matched fields: * 0: Full token * 1: Everything following '%' * 2: Everything following '%' unless '%' * 3: Argument specifier and '$' * 4: Argument specifier * 5: Type specifier * 6: '%' if full token is '%%' */ return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format); } /** * Add to the current date and time. * Usage: * $query->select($query->dateAdd()); * Prefixing the interval with a - (negative sign) will cause subtraction to be used. * Note: Not all drivers support all units. * * @param string $date The SQL-formatted date to add to. May be a date or datetime string. * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * * @return string The string with the appropriate sql for addition of dates * * @see http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add */ public function dateAdd($date, $interval, $datePart) { return trim("DATE_ADD('" . $date . "', INTERVAL " . $interval . ' ' . $datePart . ')'); } /** * Add a query to UNION ALL with the current query. * Multiple unions each require separate statements and create an array of unions. * * Usage: * $query->union('SELECT name FROM #__foo') * $query->union('SELECT name FROM #__foo','distinct') * $query->union(array('SELECT name FROM #__foo','SELECT name FROM #__bar')) * * @param mixed $query The Base object or string to union. * @param boolean $distinct True to only return distinct rows from the union. * @param string $glue The glue by which to join the conditions. * * @return mixed The Base object on success or boolean false on failure. */ public function unionAll($query, $distinct = false, $glue = '') { $glue = ')' . PHP_EOL . 'UNION ALL ('; $name = 'UNION ALL ()'; // Get the QueryElement if it does not exist if (is_null($this->unionAll)) { $this->unionAll = new QueryElement($name, $query, "$glue"); } // Otherwise append the second UNION. else { $glue = ''; $this->unionAll->append($query); } return $this; } } Driver/Query/Mysqli.php 0000604 00000005644 15245670452 0011112 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Base as BaseQuery; /** * Query Building Class. * * Based on Joomla! Platform 11.3 */ class Mysqli extends Base implements Limitable { /** * @var integer The offset for the result set. */ protected $offset; /** * @var integer The limit for the result set. */ protected $limit; /** * Method to modify a query already in string format with the needed * additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string */ public function processLimit($query, $limit, $offset = 0) { if ($limit > 0 || $offset > 0) { $query .= ' LIMIT ' . $offset . ', ' . $limit; } return $query; } /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return BaseQuery Returns this object to allow chaining. */ public function setLimit($limit = 0, $offset = 0) { $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } /** * Concatenates an array of column names or values. * * @param array $values An array of values to concatenate. * @param string $separator As separator to place between each value. * * @return string The concatenated values. */ public function concatenate($values, $separator = null) { if ($separator) { $concat_string = 'CONCAT_WS(' . $this->quote($separator); foreach ($values as $value) { $concat_string .= ', ' . $value; } return $concat_string . ')'; } else { return 'CONCAT(' . implode(',', $values) . ')'; } } } Driver/Query/Pdomysql.php 0000604 00000001767 15245670452 0011446 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); /** * Query Building Class. * * Based on Joomla! Platform 11.3 */ class Pdomysql extends Mysqli implements Limitable { } Driver/Query/Limitable.php 0000604 00000004427 15245670452 0011534 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Base as BaseQuery; /** * Query Limitable Interface. * Adds bind/unbind methods as well as a getBounded() method * to retrieve the stored bounded variables on demand prior to * query execution. * * Based on Joomla! Platform 11.2 */ interface Limitable { /** * Method to modify a query already in string format with the needed * additions to make the query limited to a particular number of * results, or start at a particular offset. This method is used * automatically by the __toString() method if it detects that the * query implements the Limitable interface. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 12.1 */ public function processLimit($query, $limit, $offset = 0); /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return BaseQuery Returns this object to allow chaining. * * @since 12.1 */ public function setLimit($limit = 0, $offset = 0); } Driver/Query/Mysql.php 0000604 00000001737 15245670452 0010740 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); /** * Query Building Class. * * Based on Joomla! Platform 11.3 */ class Mysql extends Mysqli { } Driver/Query/Element.php 0000604 00000005235 15245670452 0011221 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); /** * Query Element Class. * * Based on Joomla! Platform 11.3 */ class Element { /** * @var string The name of the element. */ protected $name = null; /** * @var array An array of elements. */ protected $elements = null; /** * @var string Glue piece. */ protected $glue = null; /** * Constructor. * * @param string $name The name of the element. * @param mixed $elements String or array. * @param string $glue The glue for elements. */ public function __construct($name, $elements, $glue = ',') { $this->elements = []; $this->name = $name; $this->glue = $glue; $this->append($elements); } /** * Magic function to convert the query element to a string. * * @return string */ public function __toString() { if (substr($this->name, -2) == '()') { return PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')'; } else { return PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements); } } /** * Appends element parts to the internal list. * * @param mixed $elements String or array. * * @return void */ public function append($elements) { if (is_array($elements)) { $this->elements = array_merge($this->elements, $elements); } else { $this->elements = array_merge($this->elements, [$elements]); } } /** * Gets the elements of this element. * * @return string */ public function getElements() { return $this->elements; } /** * Method to provide deep copy support to nested objects and arrays * when cloning. * * @return void */ public function __clone() { foreach ($this as $k => $v) { if (is_object($v) || is_array($v)) { $this->{$k} = unserialize(serialize($v)); } } } } Driver/Query/Sqlite.php 0000604 00000014741 15245670452 0011073 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); use PDO; use stdClass; /** * SQLite Query Building Class. * * @since 1.0 */ class Sqlite extends Base implements Preparable, Limitable { /** * The limit for the result set. * * @var integer * @since 1.0 */ protected $limit; /** * The offset for the result set. * * @var integer * @since 1.0 */ protected $offset; /** * Holds key / value pair of bound objects. * * @var mixed * @since 1.0 */ protected $bounded = []; /** * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also * removes a variable that has been bounded from the internal bounded array when the passed in value is null. * * @param string|integer $key The key that will be used in your SQL query to reference the value. Usually of * the form ':key', but can also be an integer. * @param mixed &$value The value that will be bound. The value is passed by reference to support output * parameters such as those possible with stored procedures. * @param integer $dataType Constant corresponding to a SQL datatype. * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return Sqlite Returns this object to allow chaining. * * @since 1.0 */ public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = []) { // Case 1: Empty Key (reset $bounded array) if (empty($key)) { $this->bounded = []; return $this; } // Case 2: Key Provided, null value (unset key from $bounded array) if (is_null($value)) { if (isset($this->bounded[$key])) { unset($this->bounded[$key]); } return $this; } $obj = new stdClass; $obj->value = &$value; $obj->dataType = $dataType; $obj->length = $length; $obj->driverOptions = $driverOptions; // Case 3: Simply add the Key/Value into the bounded array $this->bounded[$key] = $obj; return $this; } /** * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is * returned. * * @param mixed $key The bounded variable key to retrieve. * * @return mixed * * @since 1.0 */ public function &getBounded($key = null) { if (empty($key)) { return $this->bounded; } else { if (isset($this->bounded[$key])) { return $this->bounded[$key]; } } } /** * Gets the number of characters in a string. * * Note, use 'length' to find the number of bytes in a string. * * Usage: * $query->select($query->charLength('a')); * * @param string $field A value. * @param string $operator Comparison operator between charLength integer value and $condition * @param string $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 1.1.0 */ public function charLength($field, $operator = null, $condition = null) { return 'length(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : ''); } /** * Clear data from the query or a specific clause of the query. * * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query. * * @return Sqlite Returns this object to allow chaining. * * @since 1.0 */ public function clear($clause = null) { switch ($clause) { case null: $this->bounded = []; break; } return parent::clear($clause); } /** * Concatenates an array of column names or values. * * Usage: * $query->select($query->concatenate(array('a', 'b'))); * * @param array $values An array of values to concatenate. * @param string $separator As separator to place between each value. * * @return string The concatenated values. * * @since 1.1.0 */ public function concatenate($values, $separator = null) { if ($separator) { return implode(' || ' . $this->quote($separator) . ' || ', $values); } else { return implode(' || ', $values); } } /** * Method to modify a query already in string format with the needed * additions to make the query limited to a particular number of * results, or start at a particular offset. This method is used * automatically by the __toString() method if it detects that the * query implements the LimitableInterface. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 1.0 */ public function processLimit($query, $limit, $offset = 0) { if ($limit > 0 || $offset > 0) { $query .= ' LIMIT ' . $offset . ', ' . $limit; } return $query; } /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return Sqlite Returns this object to allow chaining. * * @since 1.0 */ public function setLimit($limit = 0, $offset = 0) { $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } } Driver/Query/Preparable.php 0000604 00000005067 15245670452 0011710 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver\Query; defined('AKEEBAENGINE') || die(); use PDO; /** * Database Query Preparable Interface. * * Adds bind/unbind methods as well as a getBounded() method * to retrieve the stored bounded variables on demand prior to * query execution. * * @since 1.0 * * @codeCoverageIgnore */ interface Preparable { /** * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also * removes a variable that has been bounded from the internal bounded array when the passed in value is null. * * @param string|integer $key The key that will be used in your SQL query to reference the value. Usually of * the form ':key', but can also be an integer. * @param mixed &$value The value that will be bound. The value is passed by reference to support output * parameters such as those possible with stored procedures. * @param integer $dataType Constant corresponding to a SQL datatype. * @param integer $length The length of the variable. Usually required for OUTPUT parameters. * @param array $driverOptions Optional driver options to be used. * * @return Preparable * * @since 1.0 */ public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = []); /** * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is * returned. * * @param mixed $key The bounded variable key to retrieve. * * @return mixed * * @since 1.0 */ public function &getBounded($key = null); } Driver/Base.php 0000604 00000115134 15245670452 0007375 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Base as QueryBase; use RuntimeException; /** * Database driver superclass. Used as the base of all Akeeba Engine database drivers. * Strongly based on Joomla Platform's JDatabase class. * * @method qn(string $name, string $as = null) Alias for quoteName * @method q(string $text, bool $escape = true) Alias for quote */ #[\AllowDynamicProperties] abstract class Base { /** @var string The minimum supported database version. */ protected static $dbMinimum; /** @var array JDatabaseDriver instances container. */ protected static $instances = []; /** @var string The name of the database driver. */ public $name; /** @var string The name of the database. */ protected $_database; /** @var resource The db connection resource */ protected $connection = ''; /** @var integer The number of SQL statements executed by the database driver. */ protected $count = 0; /** @var resource The database connection cursor from the last query. */ protected $cursor; /** @var boolean The database driver debugging state. */ protected $debug = false; /** @var string Driver type. This should always be mysql as we don't support anything else anymore. */ protected $driverType = ''; /** @var string The db server's error string */ protected $errorMsg = ''; /** @var int The db server's error number */ protected $errorNum = 0; /** @var int Query's limit */ protected $limit = 0; /** @var array The log of executed SQL statements by the database driver. */ protected $log = []; /** @var string Quote for named objects */ protected $nameQuote = ''; /** @var string The null or zero representation of a timestamp for the database driver. */ protected $nullDate; /** @var int Query's offset */ protected $offset = 0; /** @var array Passed in upon instantiation and saved. */ protected $options; /** @var mixed The SQL query string */ protected $sql = ''; /** @var string The prefix used in the database, if any */ protected $tablePrefix = ''; /** @var bool Support for UTF-8 */ protected $utf = true; /** * Database object constructor * * @param array $options List of options used to configure the connection */ public function __construct($options) { $prefix = array_key_exists('prefix', $options) ? $options['prefix'] : ''; $database = array_key_exists('database', $options) ? $options['database'] : ''; $connection = array_key_exists('connection', $options) ? $options['connection'] : null; $this->tablePrefix = $prefix; $this->_database = $database; $this->connection = $connection; $this->errorNum = 0; $this->count = 0; $this->log = []; $this->options = $options; } /** * Is this driver class supported on this server? Child classes are supposed to override this and perform a * compatibility check. * * @return bool True if the driver class is supported on the server */ public static function isSupported() { return false; } /** * Splits a string of multiple queries into an array of individual queries. * * @param string $query Input SQL string with which to split into individual queries. * * @return array The queries from the input string separated into an array. */ public static function splitSql($query) { $start = 0; $open = false; $char = ''; $end = strlen($query); $queries = []; for ($i = 0; $i < $end; $i++) { $current = substr($query, $i, 1); if (($current == '"' || $current == '\'')) { $n = 2; while (substr($query, $i - $n + 1, 1) == '\\' && $n < $i) { $n++; } if ($n % 2 == 0) { if ($open) { if ($current == $char) { $open = false; $char = ''; } } else { $open = true; $char = $current; } } } if (($current == ';' && !$open) || $i == $end - 1) { $queries[] = substr($query, $start, ($i - $start + 1)); $start = $i + 1; } } return $queries; } /** * Is this driver supported under the current system configuration? * * @return bool */ public static function test() { return self::isSupported(); } /** * Magic method to provide method alias support for quote() and quoteName(). * * @param string $method The called method. * @param array $args The array of arguments passed to the method. * * @return string The aliased method's return value or null. */ public function __call($method, $args) { if (empty($args)) { return null; } switch ($method) { case 'q': return $this->quote($args[0], $args[1] ?? true); break; case 'nq': case 'qn': return $this->quoteName($args[0]); break; } return null; } /** * Database object destructor * * @return bool */ public function __destruct() { return $this->close(); } public function __wakeup() { $this->open(); } /** * By default, when the object is shutting down, the connection is closed */ public function _onSerialize() { $this->close(); } /** * Alter database's character set, obtaining query string from protected member. * * @param string $dbName The database name that will be altered * * @return string The query that alter the database query string * * @throws RuntimeException */ public function alterDbCharacterSet($dbName) { if (is_null($dbName)) { throw new RuntimeException('Database name must not be null.'); } $this->setQuery($this->getAlterDbCharacterSet($dbName)); return $this->execute(); } /** * Closes the database connection */ abstract public function close(); /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. */ abstract public function connected(); /** * Create a new database using information from $options object, obtaining query string * from protected member. * * @param object $options Object used to pass user and database name to database driver. * This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return string The query that creates database * * @throws RuntimeException */ public function createDatabase($options, $utf = true) { if (is_null($options)) { throw new RuntimeException('$options object must not be null.'); } elseif (empty($options->db_name)) { throw new RuntimeException('$options object must have db_name set.'); } elseif (empty($options->db_user)) { throw new RuntimeException('$options object must have db_user set.'); } $this->setQuery($this->getCreateDatabaseQuery($options, $utf)); return $this->execute(); } /** * Drops a table from the database. * * @param string $table The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return Base Returns this object to support chaining. */ public abstract function dropTable($table, $ifExists = true); /** * 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. */ abstract public function escape($text, $extra = false); /** * An alias for query() * * @return mixed A database cursor resource on success, boolean false on failure. */ public function execute() { return $this->query(); } /** * 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. */ abstract public function fetchAssoc($cursor = null); /** * 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 */ abstract public function freeResult($cursor = null); /** * Returns the abstracted name of a database object * * @param string $tableName * * @return string */ public function getAbstract($tableName) { $prefix = $this->getPrefix(); // Don't return abstract names for non-CMS tables if (is_null($prefix)) { return $tableName; } switch ($prefix) { case '': // This is more of a hack; it assumes all tables are CMS tables if the prefix is empty. return '#__' . $tableName; break; default: // Normal behaviour for 99% of sites $tableAbstract = $tableName; if (!empty($prefix)) { if (substr($tableName, 0, strlen($prefix)) == $prefix) { $tableAbstract = '#__' . substr($tableName, strlen($prefix)); } else { $tableAbstract = $tableName; } } return $tableAbstract; break; } } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. */ abstract public function getAffectedRows(); /** * 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 or boolean false if not supported. */ abstract public function getCollation(); /** * Method that provides access to the underlying database connection. * * @return resource The underlying database connection resource. */ public function getConnection() { return $this->connection; } /** * Inherits the connection of another database driver. Useful for cloning * the CMS database connection into an Akeeba Engine database driver. * * @param resource $connection */ public function setConnection($connection) { $this->connection = $connection; } /** * Get the total number of SQL statements executed by the database driver. * * @return integer * * @since 11.1 */ public function getCount() { return $this->count; } /** * Returns a PHP date() function compliant date format for the database driver. * * @return string The format string. */ public function getDateFormat() { return 'Y-m-d H:i:s'; } /** * Return the database driver type, e.g. "mysql" for all drivers which can talk to MySQL * * @return string */ public function getDriverType() { return $this->driverType; } /** * Get the error message * * @return string The error message for the most recent query */ public function getErrorMsg($escaped = false) { if ($escaped) { return addslashes($this->errorMsg); } else { return $this->errorMsg; } } /** * Get the error number * * @return int The error number for the most recent query */ public function getErrorNum() { return $this->errorNum; } /** * 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. */ public function getEscaped($text, $extra = false) { return $this->escape($text, $extra); } /** * Get the database driver SQL statement log. * * @return array SQL statements executed by the database driver. * * @since 11.1 */ public function getLog() { return $this->log; } /** * Get the minimum supported database version. * * @return string The minimum version number for the database driver. * * @since 12.1 */ public function getMinimum() { return static::$dbMinimum; } /** * Get the null or zero representation of a timestamp for the database driver. * * @return string Null or zero representation of a timestamp. */ public function getNullDate() { return $this->nullDate; } /** * 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. */ abstract public function getNumRows($cursor = null); /** * Get the database table prefix * * @return string The database prefix */ public function getPrefix() { return $this->tablePrefix; } /** * Get the current query object or a new QueryBase object. * * @param boolean $new False to return the current query object, True to return a new QueryBase object. * * @return QueryBase The current query object or a new object extending the QueryBase class. */ abstract public function getQuery($new = false); /** * Create a new QueryBase object. * * @return QueryBase The current query object or a new object extending the QueryBase class. */ abstract public function createQuery(); /** * Retrieves field information about the given tables. * * @param string $table The name of the database table. * @param boolean $typeOnly True (default) to only return field types. * * @return array An array of fields by table. */ abstract public function getTableColumns($table, $typeOnly = true); /** * 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. */ abstract public function getTableCreate($tables); /** * Retrieves field information about the given tables. * * @param mixed $tables A table name or a list of table names. * @param boolean $typeOnly True to only return field types. * * @return array An array of fields by table. */ public function getTableFields($tables, $typeOnly = true) { $results = []; $tables = (array) $tables; foreach ($tables as $table) { $results[$table] = $this->getTableColumns($table, $typeOnly); } return $results; } /** * Retrieves field information about the given tables. * * @param mixed $tables A table name or a list of table names. * * @return array An array of keys for the table(s). */ abstract public function getTableKeys($tables); /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. */ abstract public function getTableList(); /** * Returns an array with the names of tables, views, procedures, functions and triggers * in the database. The table names are the keys of the tables, whereas the value is * the type of each element: table, view, merge, temp, procedure, function or trigger. * Note that merge are MRG_MYISAM tables and temp is non-permanent data table, usually * set up as temporary, black hole or federated tables. These two types should never, * ever, have their data dumped in the SQL dump file. * * @param bool $abstract Return abstract or normal names? Defaults to true (abstract names) * * @return array */ abstract public function getTables($abstract = true); /** * Determine whether or not the database engine supports UTF-8 character encoding. * * @return boolean True if the database engine supports UTF-8 character encoding. */ public function getUTFSupport() { return $this->utf; } /** * Get the version of the database connector * * @return string The database connector version. */ abstract public function getVersion(); /** * Determines if the database engine supports UTF-8 character encoding. * * @return boolean True if supported. */ public function hasUTF() { return $this->utf; } /** * Determine whether or not the database engine supports UTF-8 character encoding. * * @return boolean True if the database engine supports UTF-8 character encoding. */ public function hasUTFSupport() { return $this->utf; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object &$object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean True on success. */ public function insertObject($table, &$object, $key = null) { $fields = []; $values = []; // Iterate over the object variables to build the query fields and values. foreach (get_object_vars($object) as $k => $v) { // Only process non-null scalars. if (is_array($v) || is_object($v) || ($v === null)) { continue; } // Ignore any internal fields. if ($k[0] == '_') { continue; } // Prepare and sanitize the fields and values for the database query. $fields[] = $this->quoteName($k); $values[] = $this->quote($v); } // Create the base insert statement. $query = $this->getQuery(true) ->insert($this->quoteName($table)) ->columns($fields) ->values(implode(',', $values)); // Set the query and execute the insert. $this->setQuery($query); if (!$this->execute()) { return false; } // Update the primary key if it exists. $id = $this->insertid(); if ($key && $id && is_string($key)) { $object->$key = $id; } return true; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. */ abstract public function insertid(); /** * Method to check whether the installed database version is supported by the database driver * * @return boolean True if the database version is supported * * @since 12.1 */ public function isMinimumVersion() { return version_compare($this->getVersion(), static::$dbMinimum) >= 0; } /** * Method to get the first row of the result set from the database query as an associative array * of ['field_name' => 'row_value']. * * @return mixed The return value or null if the query failed. */ public function loadAssoc() { $ret = null; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get the first row from the result set as an associative array. if ($array = $this->fetchAssoc($cursor)) { $ret = $array; } // Free up system resources and return. $this->freeResult($cursor); return $ret; } /** * Method to get an array of the result set rows from the database query where each row is an associative array * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to * a sequential numeric array. * * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted * behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * @param string $column An optional column name. Instead of the whole row, only this column value will be in * the result array. * * @return mixed The return value or null if the query failed. */ public function loadAssocList($key = null, $column = null) { $array = []; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get all of the rows from the result set. while ($row = $this->fetchAssoc($cursor)) { $value = ($column) ? ($row[$column] ?? $row) : $row; if ($key) { $array[$row[$key]] = $value; } else { $array[] = $value; } } // Free up system resources and return. $this->freeResult($cursor); return $array; } /** * Method to get an array of values from the <var>$offset</var> field in each row of the result set from * the database query. * * @param integer $offset The row offset to use to build the result array. * * @return mixed The return value or null if the query failed. */ public function loadColumn($offset = 0) { $array = []; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get all of the rows from the result set as arrays. while ($row = $this->fetchArray($cursor)) { $array[] = $row[$offset]; } // Free up system resources and return. $this->freeResult($cursor); return $array; } /** * Method to get the next row in the result set from the database query as an object. * * @param string $class The class name to use for the returned row object. * * @return mixed The result of the query as an array, false if there are no more rows. */ public function loadNextObject($class = 'stdClass') { // Execute the query and get the result set cursor. if (is_null($this->cursor)) { if (!($this->cursor = $this->execute())) { return $this->errorNum ? null : false; } } // Get the next row from the result set as an object of type $class. if ($row = $this->fetchObject($this->cursor, $class)) { return $row; } // Free up system resources and return. $this->freeResult($this->cursor); $this->cursor = null; return false; } /** * Method to get the next row in the result set from the database query as an array. * * @return mixed The result of the query as an array, false if there are no more rows. */ public function loadNextRow() { // Execute the query and get the result set cursor. if (is_null($this->cursor)) { if (!($this->cursor = $this->execute())) { return $this->errorNum ? null : false; } } // Get the next row from the result set as an object of type $class. if ($row = $this->fetchArray($this->cursor)) { return $row; } // Free up system resources and return. $this->freeResult($this->cursor); $this->cursor = null; return false; } /** * Method to get the first row of the result set from the database query as an object. * * @param string $class The class name to use for the returned row object. * * @return mixed The return value or null if the query failed. */ public function loadObject($class = 'stdClass') { $ret = null; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get the first row from the result set as an object of type $class. if ($object = $this->fetchObject($cursor, $class)) { $ret = $object; } // Free up system resources and return. $this->freeResult($cursor); return $ret; } /** * Method to get an array of the result set rows from the database query where each row is an object. The array * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted * behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * @param string $class The class name to use for the returned row objects. * * @return mixed The return value or null if the query failed. */ public function loadObjectList($key = '', $class = 'stdClass') { $array = []; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get all of the rows from the result set as objects of type $class. while ($row = $this->fetchObject($cursor, $class)) { if ($key) { $array[$row->$key] = $row; } else { $array[] = $row; } } // Free up system resources and return. $this->freeResult($cursor); return $array; } /** * Method to get the first field of the first row of the result set from the database query. * * @return mixed The return value or null if the query failed. */ public function loadResult() { $ret = null; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get the first row from the result set as an array. if ($row = $this->fetchArray($cursor)) { $ret = $row[0]; } // Free up system resources and return. $this->freeResult($cursor); return $ret; } /** * Method to get an array of values from the <var>$offset</var> field in each row of the result set from * the database query. * * @param integer $offset The row offset to use to build the result array. * * @return mixed The return value or null if the query failed. */ public function loadResultArray($offset = 0) { return $this->loadColumn($offset); } /** * Method to get the first row of the result set from the database query as an array. Columns are indexed * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc. * * @return mixed The return value or null if the query failed. */ public function loadRow() { $ret = null; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get the first row from the result set as an array. if ($row = $this->fetchArray($cursor)) { $ret = $row; } // Free up system resources and return. $this->freeResult($cursor); return $ret; } /** * Method to get an array of the result set rows from the database query where each row is an array. The array * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array. * * NOTE: Choosing to key the result array by a non-unique field can result in unwanted * behavior and should be avoided. * * @param string $key The name of a field on which to key the result array. * * @return mixed The return value or null if the query failed. */ public function loadRowList($key = null) { $array = []; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get all of the rows from the result set as arrays. while ($row = $this->fetchArray($cursor)) { if ($key !== null) { $array[$row[$key]] = $row; } else { $array[] = $row; } } // Free up system resources and return. $this->freeResult($cursor); return $array; } /** * Locks a table in the database. * * @param string $tableNameName The name of the table to unlock. * * @return Base Returns this object to support chaining. */ public abstract function lockTable($tableNameName); /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. * * @param string $name The identifier name to wrap in quotes. * * @return string The quote wrapped name. */ public function nameQuote($name) { return $this->quoteName($name); } /** * Opens a database connection. It MUST be overriden by children classes * * @return Base */ public function open() { // Don't try to reconnect if we're already connected if (is_resource($this->connection) && !is_null($this->connection)) { return $this; } // Determine utf-8 support $this->utf = $this->hasUTF(); // Set charactersets (needed for MySQL 4.1.2+) if ($this->utf) { $this->setUTF(); } // Select the current database $this->select($this->_database); return $this; } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. */ abstract public function query(); /** * Method to quote and optionally escape a string to database requirements for insertion into the database. * * @param string $text The string to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. */ public function quote($text, $escape = true) { return '\'' . ($escape ? $this->escape($text) : $text) . '\''; } /** * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection * risks and reserved word conflicts. * * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in * quotes. Each type supports dot-notation name. * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it * has to be same length of $name; if is null there will not be any AS part for string * or array element. */ public function quoteName($name, $as = null) { if (!is_array($name)) { $quotedName = $this->quoteNameStr(explode('.', $name)); $quotedAs = ''; if (!is_null($as)) { $as = (array) $as; $quotedAs .= ' AS ' . $this->quoteNameStr($as); } return $quotedName . $quotedAs; } else { $fin = []; if (is_null($as)) { foreach ($name as $str) { $fin[] = $this->quoteName($str); } } elseif (is_array($name) && (count($name) == (is_array($as) || $as instanceof \Countable ? count($as) : 0))) { $count = count($name); for ($i = 0; $i < $count; $i++) { $fin[] = $this->quoteName($name[$i], $as[$i]); } } return $fin; } } /** * 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 Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return Base Returns this object to support chaining. */ public abstract function renameTable($oldTable, $newTable, $backup = null, $prefix = null); /** * This function replaces a string identifier <var>$prefix</var> with the string held is the * <var>tablePrefix</var> class variable. * * @param string $query The SQL statement to prepare. * @param string $prefix The common table prefix. * * @return string The processed SQL statement. */ public function replacePrefix($query, $prefix = '#__') { $escaped = false; $startPos = 0; $quoteChar = ''; $literal = ''; $query = trim($query); $n = strlen($query); while ($startPos < $n) { $ip = strpos($query, $prefix, $startPos); if ($ip === false) { break; } $j = strpos($query, "'", $startPos); $k = strpos($query, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } $literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos)); $startPos = $j; $j = $startPos + 1; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($query, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $query[$l] == '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($query, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($query, $startPos, $n - $startPos); } return $literal; } /** * Resets the error condition in the driver. Useful to reset the error state after handling a thrown exception. * * @return $this for chaining */ public function resetErrors() { $this->errorNum = 0; $this->errorMsg = ''; 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. */ abstract public function select($database); /** * Sets the database debugging state for the driver. * * @param boolean $level True to enable debugging. * * @return boolean The old debugging level. */ public function setDebug($level) { $previous = $this->debug; $this->debug = (bool) $level; return $previous; } /** * Sets the SQL statement string for later execution. * * @param mixed $query The SQL statement to set either as a QueryBase object or a string. * @param integer $offset The affected row offset to set. * @param integer $limit The maximum affected rows to set. * * @return self This object to support method chaining. */ public function setQuery($query, $offset = 0, $limit = 0) { $this->sql = $query; $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. */ abstract public function setUTF(); /** * Method to commit a transaction. * * @return void */ abstract public function transactionCommit(); /** * Method to roll back a transaction. * * @return void */ abstract public function transactionRollback(); /** * Method to initialize a transaction. * * @return void */ abstract public function transactionStart(); /** * Method to truncate a table. * * @param string $table The table to truncate * * @return void */ public function truncateTable($table) { $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table)); $this->query(); } /** * Unlocks tables in the database. * * @return Base Returns this object to support chaining. */ public abstract function unlockTables(); /** * Updates a row in a table based on an object's properties. * * @param string $table The name of the database table to update. * @param object &$object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. * @param boolean $nulls True to update null fields or false to ignore them. * * @return boolean True on success. */ public function updateObject($table, &$object, $key, $nulls = false) { $fields = []; $where = []; if (is_string($key)) { $key = [$key]; } if (is_object($key)) { $key = (array) $key; } // Create the base update statement. $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s'; // Iterate over the object variables to build the query fields/value pairs. foreach (get_object_vars($object) as $k => $v) { // Only process scalars that are not internal fields. if (is_array($v) || is_object($v) || ($k[0] == '_')) { continue; } // Set the primary key to the WHERE clause instead of a field to update. if (in_array($k, $key)) { $where[] = $this->quoteName($k) . '=' . $this->quote($v); continue; } // Prepare and sanitize the fields and values for the database query. if ($v === null) { // If the value is null and we want to update nulls then set it. if ($nulls) { $val = 'NULL'; } // If the value is null and we do not want to update nulls then ignore this field. else { continue; } } // The field is not null so we prep it for update. else { $val = $this->quote($v); } // Add the field to be updated. $fields[] = $this->quoteName($k) . '=' . $val; } // We don't have any fields to update. if (empty($fields)) { return true; } // Set the query and execute the update. $this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where))); return $this->execute(); } /** * 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. */ abstract protected function fetchArray($cursor = null); /** * 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. */ abstract protected function fetchObject($cursor = null, $class = 'stdClass'); /** * Return the query string to alter the database character set. * * @param string $dbName The database name * * @return string The query that alter the database query string */ protected function getAlterDbCharacterSet($dbName) { $query = 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `utf8`'; return $query; } /** * Return the query string to create new Database. * Each database driver, other than MySQL, need to override this member to return correct string. * * @param object $options Object used to pass user and database name to database driver. * This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return string The query that creates database */ protected function getCreateDatabaseQuery($options, $utf) { if ($utf) { $query = 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `utf8`'; } else { $query = 'CREATE DATABASE ' . $this->quoteName($options->db_name); } return $query; } /** * Gets the name of the database used by this connection. * * @return string */ protected function getDatabase() { return $this->_database; } /** * Quote strings coming from quoteName call. * * @param array $strArr Array of strings coming from quoteName dot-explosion. * * @return string Dot-imploded string of quoted parts. */ protected function quoteNameStr($strArr) { $parts = []; $q = $this->nameQuote; foreach ($strArr as $part) { if (is_null($part)) { continue; } if (strlen($q) == 1) { $parts[] = $q . $part . $q; } else { $parts[] = $q[0] . $part . $q[1]; } } return implode('.', $parts); } } Driver/Pdomysql.php 0000604 00000044715 15245670452 0010341 0 ustar 00 <?php /** * Akeeba Engine * * @package akeebaengine * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, see * <https://www.gnu.org/licenses/>. */ namespace Akeeba\Engine\Driver; defined('AKEEBAENGINE') || die(); use Akeeba\Engine\Driver\Query\Pdomysql as QueryPdomysql; use Akeeba\Engine\FixMySQLHostname; use Exception; use PDO; use PDOException; use PDOStatement; use ReflectionClass; use RuntimeException; /** * PDO MySQL database driver for Akeeba Engine * * Based on Joomla! Platform 12.1 */ #[\AllowDynamicProperties] class Pdomysql extends Mysql { use FixMySQLHostname; /** * The default cipher suite for TLS connections. * * @var array */ protected static $defaultCipherSuite = [ 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-CBC-SHA256', 'AES256-CBC-SHA384', 'DES-CBC3-SHA', ]; /** * The name of the database driver. * * @var string */ public $name = 'pdomysql'; /** @var string Connection character set */ protected $charset = 'utf8mb4'; /** @var PDO The db connection resource */ protected $connection = null; /** @var PDOStatement The database connection cursor from the last query. */ protected $cursor; /** @var array Driver options for PDO */ protected $driverOptions = []; protected $ssl = []; /** @var bool Are we in the process of reconnecting to the database server? */ private $isReconnecting = false; /** * Database object constructor * * @param array $options List of options used to configure the connection */ public function __construct($options) { $this->driverType = 'mysql'; // Init $this->nameQuote = '`'; $options['ssl'] = $options['ssl'] ?? []; $options['ssl'] = is_array($options['ssl']) ? $options['ssl'] : []; $options['ssl']['enable'] = ($options['ssl']['enable'] ?? $options['dbencryption'] ?? false) ?: false; $options['ssl']['cipher'] = ($options['ssl']['cipher'] ?? $options['dbsslcipher'] ?? null) ?: null; $options['ssl']['ca'] = ($options['ssl']['ca'] ?? $options['dbsslca'] ?? null) ?: null; $options['ssl']['capath'] = ($options['ssl']['capath'] ?? $options['dbsslcapath'] ?? null) ?: null; $options['ssl']['key'] = ($options['ssl']['key'] ?? $options['dbsslkey'] ?? null) ?: null; $options['ssl']['cert'] = ($options['ssl']['cert'] ?? $options['dbsslcert'] ?? null) ?: null; $options['ssl']['verify_server_cert'] = ($options['ssl']['verify_server_cert'] ?? $options['dbsslverifyservercert'] ?? false) ?: false; // Figure out if a port is included in the host name $this->fixHostnamePortSocket($options['host'], $options['port'], $options['socket']); // Open the connection $this->host = $options['host'] ?? 'localhost'; $this->user = $options['user'] ?? ''; $this->password = $options['password'] ?? ''; $this->port = $options['port'] ?? ''; $this->socket = $options['socket'] ?? ''; $this->_database = $options['database'] ?? ''; $this->selectDatabase = $options['select'] ?? true; $this->ssl = $options['ssl'] ?? []; $this->charset = $options['charset'] ?? 'utf8mb4'; $this->driverOptions = $options['driverOptions'] ?? []; $this->tablePrefix = $options['prefix'] ?? ''; $this->connection = $options['connection'] ?? null; $this->errorNum = 0; $this->count = 0; $this->log = []; $this->options = $options; if (!is_object($this->connection)) { $this->open(); } } /** * Test to see if the MySQL connector is available. * * @return boolean True on success, false otherwise. */ public static function isSupported() { if (!defined('\PDO::ATTR_DRIVER_NAME')) { return false; } return in_array('mysql', PDO::getAvailableDrivers()); } /** * PDO does not support serialize * * @return array */ public function __sleep() { $serializedProperties = []; $reflect = new ReflectionClass($this); // Get properties of the current class $properties = $reflect->getProperties(); foreach ($properties as $property) { // Do not serialize properties that are \PDO if ($property->isStatic() == false && !($this->{$property->name} instanceof PDO)) { array_push($serializedProperties, $property->name); } } return $serializedProperties; } /** * Wake up after serialization * * @return array */ public function __wakeup() { // Get connection back $this->__construct($this->options); } public function close() { $return = false; if (is_object($this->cursor)) { try { $this->cursor->closeCursor(); } catch (\Throwable $e) { } } $this->connection = null; return $return; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. */ public function connected() { if (!is_object($this->connection)) { return false; } try { /** @var PDOStatement $statement */ $statement = $this->connection->prepare('SELECT 1'); $executed = $statement->execute(); $ret = 0; if ($executed) { $row = [0]; if (!empty($statement) && $statement instanceof PDOStatement) { $row = $statement->fetch(PDO::FETCH_NUM); } $ret = $row[0]; } $status = $ret == 1; $statement->closeCursor(); $statement = null; } // If we catch an exception here, we must not be connected. catch (\Throwable $e) { $status = false; } return $status; } /** * 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. */ public function escape($text, $extra = false) { if (is_int($text) || is_float($text)) { return $text; } if (is_null($text)) { return 'NULL'; } $result = substr($this->connection->quote($text), 1, -1); if ($extra) { $result = addcslashes($result, '%_'); } return $result; } /** * 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. */ public function fetchAssoc($cursor = null) { $ret = null; if (!empty($cursor) && $cursor instanceof PDOStatement) { $ret = $cursor->fetch(PDO::FETCH_ASSOC); } elseif ($this->cursor instanceof PDOStatement) { $ret = $this->cursor->fetch(PDO::FETCH_ASSOC); } return $ret; } /** * 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 */ public function freeResult($cursor = null) { if ($cursor instanceof PDOStatement) { $cursor->closeCursor(); $cursor = null; } if ($this->cursor instanceof PDOStatement) { $this->cursor->closeCursor(); $this->cursor = null; } } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. */ public function getAffectedRows() { if ($this->cursor instanceof PDOStatement) { return $this->cursor->rowCount(); } return 0; } /** * 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. */ public function getNumRows($cursor = null) { if ($cursor instanceof PDOStatement) { return $cursor->rowCount(); } if ($this->cursor instanceof PDOStatement) { return $this->cursor->rowCount(); } return 0; } /** * Get the current or query, or new JDatabaseQuery object. * * @param boolean $new False to return the last query set, True to return a new JDatabaseQuery object. * * @return mixed The current value of the internal SQL variable or a new JDatabaseQuery object. */ public function getQuery($new = false) { if ($new) { return new QueryPdomysql($this); } else { return $this->sql; } } public function createQuery() { return new QueryPdomysql($this); } /** * Get the version of the database connector. * * @return string The database connector version. */ public function getVersion() { $version = $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION); if (stripos($version, 'mariadb') !== false) { // MariaDB: Strip off any leading '5.5.5-', if present return preg_replace('/^5\.5\.5-/', '', $version); } return $version; } /** * Determines if the database engine supports UTF-8 character encoding. * * @return boolean True if supported. */ public function hasUTF() { $serverVersion = $this->getVersion(); $mariadb = stripos($serverVersion, 'mariadb') !== false; // At this point we know the client supports utf8mb4. Now we must check if the server supports utf8mb4 as well. $utf8mb4 = version_compare($serverVersion, '5.5.3', '>='); if ($mariadb && version_compare($serverVersion, '10.0.0', '<')) { $utf8mb4 = false; } return $utf8mb4; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. */ public function insertid() { // Error suppress this to prevent PDO warning us that the driver doesn't support this operation. return @$this->connection->lastInsertId(); } /** * Method to get the next row in the result set from the database query as an object. * * @param string $class The class name to use for the returned row object. * * @return mixed The result of the query as an array, false if there are no more rows. */ public function loadNextObject($class = 'stdClass') { // Execute the query and get the result set cursor. if (!$this->cursor) { if (!($this->execute())) { return $this->errorNum ? null : false; } } // Get the next row from the result set as an object of type $class. if ($row = $this->fetchObject(null, $class)) { return $row; } // Free up system resources and return. $this->freeResult(); return false; } /** * Method to get the next row in the result set from the database query as an array. * * @return mixed The result of the query as an array, false if there are no more rows. */ public function loadNextRow() { // Execute the query and get the result set cursor. if (!$this->cursor) { if (!($this->execute())) { return $this->errorNum ? null : false; } } // Get the next row from the result set as an object of type $class. if ($row = $this->fetchArray()) { return $row; } // Free up system resources and return. $this->freeResult(); return false; } public function open() { if ($this->connected()) { return; } else { $this->close(); } if (!isset($this->charset)) { $this->charset = 'utf8mb4'; } $this->port = $this->port ?: 3306; $format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#'; if ($this->socket) { $format = 'mysql:socket=#SOCKET#;dbname=#DBNAME#;charset=#CHARSET#'; } $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#', '#CHARSET#']; $with = [$this->host, $this->port, $this->socket, $this->_database, $this->charset]; // Create the connection string: $connectionString = str_replace($replace, $with, $format); // For SSL/TLS connection encryption. if ($this->ssl !== [] && $this->ssl['enable'] === true) { $sslContextIsNull = true; // If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options. foreach (['cipher', 'ca', 'capath', 'key', 'cert'] as $key => $value) { if ($this->ssl[$value] !== null) { $this->driverOptions[constant('\PDO::MYSQL_ATTR_SSL_' . strtoupper($value))] = $this->ssl[$value]; $sslContextIsNull = false; } } // PDO, if no cipher, ca, capath, cert and key are set, can't start TLS one-way connection, set a common ciphers suite to force it. if ($sslContextIsNull === true) { $this->driverOptions[\PDO::MYSQL_ATTR_SSL_CIPHER] = implode(':', static::$defaultCipherSuite); } // If customised, for capable systems (PHP 7.0.14+ and 7.1.4+) verify certificate chain and Common Name to driver options. if ($this->ssl['verify_server_cert'] !== null && defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) { $this->driverOptions[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->ssl['verify_server_cert']; } } // connect to the server try { $this->connection = new PDO( $connectionString, $this->user, $this->password, $this->driverOptions ); } catch (PDOException $e) { // If we tried connecting through utf8mb4 and we failed let's retry with regular utf8 if ($this->charset == 'utf8mb4') { $this->charset = 'UTF8'; $this->open(); return; } $this->errorNum = 2; $this->errorMsg = 'Could not connect to MySQL via PDO: ' . $e->getMessage(); return; } // Reset the SQL mode of the connection try { $this->connection->exec("SET @@SESSION.sql_mode = '';"); } // Ignore any exceptions (incompatible MySQL versions) catch (Exception $e) { } $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); if ($this->selectDatabase && !empty($this->_database)) { $this->select($this->_database); } $this->freeResult(); } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. */ public function query() { if (!is_object($this->connection)) { $this->open(); } $this->freeResult(); // 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->limit > 0 || $this->offset > 0) { $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit; } // Increment the query counter. $this->count++; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; } // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. try { $this->cursor = $this->connection->query($query); } catch (Exception $e) { } // If an error occurred handle it. if (!$this->cursor) { $errorInfo = $this->connection->errorInfo(); $this->errorNum = $errorInfo[1]; $this->errorMsg = $errorInfo[2] . ' SQL=' . $query; // Check if the server was disconnected. if (!$this->connected() && !$this->isReconnecting) { $this->isReconnecting = true; try { // Attempt to reconnect. $this->connection = null; $this->open(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { throw new RuntimeException($this->errorMsg, $this->errorNum); } // Since we were able to reconnect, run the query again. $result = $this->query(); $this->isReconnecting = false; return $result; } // The server was not disconnected. else { throw new RuntimeException($this->errorMsg, $this->errorNum); } } return $this->cursor; } /** * 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. */ public function select($database) { try { $this->connection->exec('USE ' . $this->quoteName($database)); } catch (Exception $e) { $errorInfo = $this->connection->errorInfo(); $this->errorNum = $errorInfo[1]; $this->errorMsg = $errorInfo[2]; return false; } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. */ public function setUTF() { return true; } /** * Method to commit a transaction. * * @return void */ public function transactionCommit() { $this->connection->commit(); } /** * Method to roll back a transaction. * * @return void */ public function transactionRollback() { $this->connection->rollBack(); } /** * Method to initialize a transaction. * * @return void */ public function transactionStart() { $this->connection->beginTransaction(); } /** * 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. */ protected function fetchArray($cursor = null) { $ret = null; if (!empty($cursor) && $cursor instanceof PDOStatement) { $ret = $cursor->fetch(PDO::FETCH_NUM); } elseif ($this->cursor instanceof PDOStatement) { $ret = $this->cursor->fetch(PDO::FETCH_NUM); } return $ret; } /** * 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. */ protected function fetchObject($cursor = null, $class = 'stdClass') { $ret = null; if (!empty($cursor) && $cursor instanceof PDOStatement) { $ret = $cursor->fetchObject($class); } elseif ($this->cursor instanceof PDOStatement) { $ret = $this->cursor->fetchObject($class); } return $ret; } } web.config 0000604 00000001025 15245670452 0006514 0 ustar 00 <?xml version="1.0"?> <!-- This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions --> <configuration> <system.webServer> <security> <requestFiltering> <fileExtensions allowUnlisted="false" > <clear /> <add fileExtension=".html" allowed="true"/> </fileExtensions> </requestFiltering> </security> </system.webServer> </configuration>