Current File : /home/pacjaorg/public_html/kmm/libraries/vendor/joomla/database/src/Pdo/PdoDriver.php |
<?php
/**
* Part of the Joomla Framework Database Package
*
* @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Database\Pdo;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\DatabaseEvents;
use Joomla\Database\Event\ConnectionEvent;
use Joomla\Database\Exception\ConnectionFailureException;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\Exception\PrepareStatementFailureException;
use Joomla\Database\Exception\UnsupportedAdapterException;
use Joomla\Database\StatementInterface;
/**
* Joomla Framework PDO Database Driver Class
*
* @link https://www.php.net/pdo
* @since 1.0
*/
abstract class PdoDriver extends DatabaseDriver
{
/**
* The database connection resource.
*
* @var \PDO
* @since 1.0
*/
protected $connection;
/**
* The name of the database driver.
*
* @var string
* @since 1.0
*/
public $name = 'pdo';
/**
* The character(s) used to quote SQL statement names such as table names or field names, etc.
*
* 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 = "'";
/**
* The null or zero representation of a timestamp for the database driver.
*
* @var string
* @since 1.0
*/
protected $nullDate = '0000-00-00 00:00:00';
/**
* Constructor.
*
* @param array $options List of options used to configure the connection
*
* @since 1.0
*/
public function __construct(array $options)
{
// Get some basic values from the options.
$options['driver'] = $options['driver'] ?? 'odbc';
$options['dsn'] = $options['dsn'] ?? '';
$options['host'] = $options['host'] ?? 'localhost';
$options['database'] = $options['database'] ?? '';
$options['user'] = $options['user'] ?? '';
$options['port'] = isset($options['port']) ? (int) $options['port'] : null;
$options['password'] = $options['password'] ?? '';
$options['driverOptions'] = $options['driverOptions'] ?? [];
$options['ssl'] = isset($options['ssl']) ? $options['ssl'] : [];
$options['socket'] = \strpos($options['host'], 'unix:') !== false ? \str_replace('unix:', '', $options['host']) : null;
if ($options['ssl'] !== [])
{
$options['ssl']['enable'] = isset($options['ssl']['enable']) ? $options['ssl']['enable'] : false;
$options['ssl']['cipher'] = isset($options['ssl']['cipher']) ? $options['ssl']['cipher'] : null;
$options['ssl']['ca'] = isset($options['ssl']['ca']) ? $options['ssl']['ca'] : null;
$options['ssl']['capath'] = isset($options['ssl']['capath']) ? $options['ssl']['capath'] : null;
$options['ssl']['key'] = isset($options['ssl']['key']) ? $options['ssl']['key'] : null;
$options['ssl']['cert'] = isset($options['ssl']['cert']) ? $options['ssl']['cert'] : null;
$options['ssl']['verify_server_cert'] = isset($options['ssl']['verify_server_cert']) ? $options['ssl']['verify_server_cert'] : null;
}
// Finalize initialisation
parent::__construct($options);
}
/**
* Destructor.
*
* @since 1.0
*/
public function __destruct()
{
$this->disconnect();
}
/**
* Connects to the database if needed.
*
* @return void Returns void if the database connected successfully.
*
* @since 1.0
* @throws \RuntimeException
*/
public function connect()
{
if ($this->connection)
{
return;
}
// Make sure the PDO extension for PHP is installed and enabled.
if (!static::isSupported())
{
throw new UnsupportedAdapterException('PDO Extension is not available.', 1);
}
// Find the correct PDO DSN Format to use:
switch ($this->options['driver'])
{
case 'cubrid':
$this->options['port'] = $this->options['port'] ?? 33000;
$format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['database']];
break;
case 'dblib':
$this->options['port'] = $this->options['port'] ?? 1433;
$format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['database']];
break;
case 'firebird':
$this->options['port'] = $this->options['port'] ?? 3050;
$format = 'firebird:dbname=#DBNAME#';
$replace = ['#DBNAME#'];
$with = [$this->options['database']];
break;
case 'ibm':
$this->options['port'] = $this->options['port'] ?? 56789;
if (!empty($this->options['dsn']))
{
$format = 'ibm:DSN=#DSN#';
$replace = ['#DSN#'];
$with = [$this->options['dsn']];
}
else
{
$format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['database']];
}
break;
case 'informix':
$this->options['port'] = $this->options['port'] ?? 1526;
$this->options['protocol'] = $this->options['protocol'] ?? 'onsoctcp';
if (!empty($this->options['dsn']))
{
$format = 'informix:DSN=#DSN#';
$replace = ['#DSN#'];
$with = [$this->options['dsn']];
}
else
{
$format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#'];
$with = [
$this->options['host'],
$this->options['port'],
$this->options['database'],
$this->options['server'],
$this->options['protocol'],
];
}
break;
case 'mssql':
$this->options['port'] = $this->options['port'] ?? 1433;
$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['database']];
break;
case 'mysql':
$this->options['port'] = $this->options['port'] ?? 3306;
if ($this->options['socket'] !== null)
{
$format = 'mysql:unix_socket=#SOCKET#;dbname=#DBNAME#;charset=#CHARSET#';
}
else
{
$format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#';
}
$replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#', '#CHARSET#'];
$with = [
$this->options['host'],
$this->options['port'],
$this->options['socket'],
$this->options['database'],
$this->options['charset']
];
break;
case 'oci':
$this->options['port'] = $this->options['port'] ?? 1521;
$this->options['charset'] = $this->options['charset'] ?? 'AL32UTF8';
if (!empty($this->options['dsn']))
{
$format = 'oci:dbname=#DSN#';
$replace = ['#DSN#'];
$with = [$this->options['dsn']];
}
else
{
$format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['database']];
}
$format .= ';charset=' . $this->options['charset'];
break;
case 'odbc':
$format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#';
$replace = ['#DSN#', '#USER#', '#PASSWORD#'];
$with = [$this->options['dsn'], $this->options['user'], $this->options['password']];
break;
case 'pgsql':
$this->options['port'] = $this->options['port'] ?? 5432;
if ($this->options['socket'] !== null)
{
$format = 'pgsql:host=#SOCKET#;dbname=#DBNAME#';
}
else
{
$format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
}
$replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['socket'], $this->options['database']];
// For data in transit TLS encryption.
if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true)
{
if (isset($this->options['ssl']['verify_server_cert']) && $this->options['ssl']['verify_server_cert'] === true)
{
$format .= ';sslmode=verify-full';
}
else
{
$format .= ';sslmode=require';
}
$sslKeysMapping = [
'cipher' => null,
'ca' => 'sslrootcert',
'capath' => null,
'key' => 'sslkey',
'cert' => 'sslcert',
];
// If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options.
foreach ($sslKeysMapping as $key => $value)
{
if ($value !== null && $this->options['ssl'][$key] !== null)
{
$format .= ';' . $value . '=' . $this->options['ssl'][$key];
}
}
}
break;
case 'sqlite':
if (isset($this->options['version']) && $this->options['version'] == 2)
{
$format = 'sqlite2:#DBNAME#';
}
else
{
$format = 'sqlite:#DBNAME#';
}
$replace = ['#DBNAME#'];
$with = [$this->options['database']];
break;
case 'sybase':
$this->options['port'] = $this->options['port'] ?? 1433;
$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';
$replace = ['#HOST#', '#PORT#', '#DBNAME#'];
$with = [$this->options['host'], $this->options['port'], $this->options['database']];
break;
default:
throw new UnsupportedAdapterException('The ' . $this->options['driver'] . ' driver is not supported.');
}
// Create the connection string:
$connectionString = str_replace($replace, $with, $format);
try
{
$this->connection = new \PDO(
$connectionString,
$this->options['user'],
$this->options['password'],
$this->options['driverOptions']
);
}
catch (\PDOException $e)
{
throw new ConnectionFailureException('Could not connect to PDO: ' . $e->getMessage(), $e->getCode(), $e);
}
$this->setOption(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_CONNECT, $this));
}
/**
* Method to escape a string for usage in an SQL statement.
*
* Oracle escaping reference:
* http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
*
* SQLite escaping notes:
* http://www.sqlite.org/faq.html#q14
*
* Method body is as implemented by the Zend Framework
*
* 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))
{
return $text;
}
if (\is_float($text))
{
// Force the dot as a decimal point.
return str_replace(',', '.', (string) $text);
}
$text = str_replace("'", "''", (string) $text);
return addcslashes($text, "\000\n\r\\\032");
}
/**
* Execute the SQL statement.
*
* @return boolean
*
* @since 1.0
* @throws \Exception
* @throws \RuntimeException
*/
public function execute()
{
$this->connect();
// Take a local copy so that we don't modify the original query and cause issues later
$sql = $this->replacePrefix((string) $this->sql);
// Increment the query counter.
$this->count++;
// Get list of bounded parameters
$bounded =& $this->sql->getBounded();
// If there is a monitor registered, let it know we are starting this query
if ($this->monitor)
{
$this->monitor->startQuery($sql, $bounded);
}
// Execute the query.
$this->executed = false;
// Bind the variables
foreach ($bounded as $key => $obj)
{
$this->statement->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
}
try
{
$this->executed = $this->statement->execute();
// If there is a monitor registered, let it know we have finished this query
if ($this->monitor)
{
$this->monitor->stopQuery();
}
return true;
}
catch (\PDOException $exception)
{
// If there is a monitor registered, let it know we have finished this query
if ($this->monitor)
{
$this->monitor->stopQuery();
}
// Get the error number and message before we execute any more queries.
$errorNum = (int) $this->statement->errorCode();
$errorMsg = (string) implode(', ', $this->statement->errorInfo());
// Check if the server was disconnected.
try
{
if (!$this->connected())
{
try
{
// Attempt to reconnect.
$this->connection = null;
$this->connect();
}
catch (ConnectionFailureException $e)
{
// If connect fails, ignore that exception and throw the normal exception.
throw new ExecutionFailureException($sql, $errorMsg, $errorNum);
}
// Since we were able to reconnect, run the query again.
return $this->execute();
}
}
catch (\LogicException $e)
{
throw new ExecutionFailureException($sql, $errorMsg, $errorNum, $e);
}
// Throw the normal query exception.
throw new ExecutionFailureException($sql, $errorMsg, $errorNum);
}
}
/**
* Retrieve a PDO database connection attribute
* https://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->connect();
return $this->connection->getAttribute($key);
}
/**
* Get the version of the database connector.
*
* @return string The database connector version.
*
* @since 1.5.0
*/
public function getVersion()
{
$this->connect();
return $this->getOption(\PDO::ATTR_SERVER_VERSION);
}
/**
* Get a query to run and verify the database is operational.
*
* @return string The query to check the health of the DB.
*
* @since 1.0
*/
public function getConnectedQuery()
{
return 'SELECT 1';
}
/**
* Sets an attribute on the PDO database handle.
* https://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->connect();
return $this->connection->setAttribute($key, $value);
}
/**
* Test to see if the PDO extension is available.
* Override as needed to check for specific PDO Drivers.
*
* @return boolean True on success, false otherwise.
*
* @since 1.0
*/
public static function isSupported()
{
return \defined('\\PDO::ATTR_DRIVER_NAME');
}
/**
* Determines if the connection to the server is active.
*
* @return boolean True if connected to the database engine.
*
* @since 1.0
* @throws \LogicException
*/
public function connected()
{
// Flag to prevent recursion into this function.
static $checkingConnected = false;
if ($checkingConnected)
{
// Reset this flag and throw an exception.
$checkingConnected = false;
throw new \LogicException('Recursion trying to check if connected.');
}
// Backup the query state.
$sql = $this->sql;
$limit = $this->limit;
$offset = $this->offset;
$statement = $this->statement;
try
{
// Set the checking connection flag.
$checkingConnected = true;
// Run a simple query to check the connection.
$this->setQuery($this->getConnectedQuery());
$status = (bool) $this->loadResult();
}
catch (\Exception $e)
{
// If we catch an exception here, we must not be connected.
$status = false;
}
// Restore the query state.
$this->sql = $sql;
$this->limit = $limit;
$this->offset = $offset;
$this->statement = $statement;
$checkingConnected = false;
return $status;
}
/**
* Method to get the auto-incremented value from the last INSERT statement.
*
* @return string The value of the auto-increment field from the last inserted row.
*
* @since 1.0
*/
public function insertid()
{
$this->connect();
// Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
return @$this->connection->lastInsertId();
}
/**
* Select a database for use.
*
* @param string $database The name of the database to select for use.
*
* @return boolean True if the database was successfully selected.
*
* @since 1.0
* @throws \RuntimeException
*/
public function select($database)
{
$this->connect();
return true;
}
/**
* Set the connection to use UTF-8 character encoding.
*
* @return boolean True on success.
*
* @since 1.0
*/
public function setUtf()
{
return false;
}
/**
* Method to commit a transaction.
*
* @param boolean $toSavepoint If true, commit to the last savepoint.
*
* @return void
*
* @since 1.0
* @throws \RuntimeException
*/
public function transactionCommit($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth === 1)
{
$this->connection->commit();
}
$this->transactionDepth--;
}
/**
* Method to roll back a transaction.
*
* @param boolean $toSavepoint If true, rollback to the last savepoint.
*
* @return void
*
* @since 1.0
* @throws \RuntimeException
*/
public function transactionRollback($toSavepoint = false)
{
$this->connect();
if (!$toSavepoint || $this->transactionDepth === 1)
{
$this->connection->rollBack();
}
$this->transactionDepth--;
}
/**
* Method to initialize a transaction.
*
* @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
*
* @return void
*
* @since 1.0
* @throws \RuntimeException
*/
public function transactionStart($asSavepoint = false)
{
$this->connect();
if (!$asSavepoint || !$this->transactionDepth)
{
$this->connection->beginTransaction();
}
$this->transactionDepth++;
}
/**
* Prepares a SQL statement for execution
*
* @param string $query The SQL query to be prepared.
*
* @return StatementInterface
*
* @since 2.0.0
* @throws PrepareStatementFailureException
*/
protected function prepareStatement(string $query): StatementInterface
{
try
{
return new PdoStatement($this->connection->prepare($query, $this->options['driverOptions']));
}
catch (\PDOException $exception)
{
throw new PrepareStatementFailureException($exception->getMessage(), $exception->getCode(), $exception);
}
}
/**
* PDO does not support serialize
*
* @return array
*
* @since 1.0
*/
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))
{
$serializedProperties[] = $property->name;
}
}
return $serializedProperties;
}
/**
* Wake up after serialization
*
* @return void
*
* @since 1.0
*/
public function __wakeup()
{
// Get connection back
$this->__construct($this->options);
}
}