Import Ruty

This commit is contained in:
2024-03-11 00:58:34 +01:00
parent 34a31bb184
commit 985f1ab418
618 changed files with 225414 additions and 0 deletions
@@ -0,0 +1,185 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for MS SQL Server database |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
*
* @package Framework
* @subpackage Database
*/
class rcube_db_mssql extends rcube_db
{
public $db_provider = 'mssql';
/**
* {@inheritdoc}
*/
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
{
parent::__construct($db_dsnw, $db_dsnr, $pconn);
$this->options['identifier_start'] = '[';
$this->options['identifier_end'] = ']';
}
/**
* Driver-specific configuration of database connection
*
* @param array $dsn DSN for DB connections
* @param PDO $dbh Connection handler
*/
protected function conn_configure($dsn, $dbh)
{
// Set date format in case of non-default language (#1488918)
$dbh->query("SET DATEFORMAT ymd");
}
/**
* Return SQL function for current time and date
*
* @param int $interval Optional interval (in seconds) to add/subtract
*
* @return string SQL function to use in query
*/
public function now($interval = 0)
{
if ($interval) {
$interval = intval($interval);
return "dateadd(second, $interval, getdate())";
}
return "getdate()";
}
/**
* Return SQL statement to convert a field value into a unix timestamp
*
* @param string $field Field name
*
* @return string SQL statement to use in query
* @deprecated
*/
public function unixtimestamp($field)
{
return "DATEDIFF(second, '19700101', $field) + DATEDIFF(second, GETDATE(), GETUTCDATE())";
}
/**
* Abstract SQL statement for value concatenation
*
* @return string ...$args Values to concatenate
*/
public function concat(...$args)
{
if (count($args) == 1 && is_array($args[0])) {
$args = $args[0];
}
return '(' . implode('+', $args) . ')';
}
/**
* Adds TOP (LIMIT,OFFSET) clause to the query
*
* @param string $query SQL query
* @param int $limit Number of rows
* @param int $offset Offset
*
* @return string SQL query
*/
protected function set_limit($query, $limit = 0, $offset = 0)
{
$limit = intval($limit);
$offset = intval($offset);
$end = $offset + $limit;
// query without OFFSET
if (!$offset) {
$query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
return $query;
}
$orderby = stristr($query, 'ORDER BY');
$offset += 1;
if ($orderby !== false) {
$query = trim(substr($query, 0, -1 * strlen($orderby)));
}
else {
// it shouldn't happen, paging without sorting has not much sense
// @FIXME: I don't know how to build paging query without ORDER BY
$orderby = "ORDER BY 1";
}
$query = preg_replace('/^SELECT\s/i', '', $query);
$query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
. " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
return $query;
}
/**
* Returns PDO DSN string from DSN array
*/
protected function dsn_string($dsn)
{
$params = [];
$result = $dsn['phptype'] . ':';
if (isset($dsn['hostspec'])) {
$host = $dsn['hostspec'];
if (isset($dsn['port'])) {
$host .= ',' . $dsn['port'];
}
$params[] = 'host=' . $host;
}
if (isset($dsn['database'])) {
$params[] = 'dbname=' . $dsn['database'];
}
if (!empty($params)) {
$result .= implode(';', $params);
}
return $result;
}
/**
* Parse SQL file and fix table names according to table prefix
*/
protected function fix_table_names($sql)
{
if (!$this->options['table_prefix']) {
return $sql;
}
// replace sequence names, and other postgres-specific commands
$sql = preg_replace_callback(
'/((TABLE|(?<!ON )UPDATE|INSERT INTO|FROM(?! deleted)| ON(?! (DELETE|UPDATE|\[PRIMARY\]))'
. '|REFERENCES|CONSTRAINT|TRIGGER|INDEX)\s+(\[dbo\]\.)?[\[\]]*)([^\[\]\( \r\n]+)/',
[$this, 'fix_table_names_callback'],
$sql
);
return $sql;
}
}
@@ -0,0 +1,245 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for MySQL database |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface
*
* This is a wrapper for the PHP PDO
*
* @package Framework
* @subpackage Database
*/
class rcube_db_mysql extends rcube_db
{
public $db_provider = 'mysql';
/**
* {@inheritdoc}
*/
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
{
parent::__construct($db_dsnw, $db_dsnr, $pconn);
// SQL identifiers quoting
$this->options['identifier_start'] = '`';
$this->options['identifier_end'] = '`';
}
/**
* Abstract SQL statement for value concatenation
*
* @return string ...$args Values to concatenate
*/
public function concat(...$args)
{
if (count($args) == 1 && is_array($args[0])) {
$args = $args[0];
}
return 'CONCAT(' . implode(', ', $args) . ')';
}
/**
* Returns PDO DSN string from DSN array
*
* @param array $dsn DSN parameters
*
* @return string Connection string
*/
protected function dsn_string($dsn)
{
$params = [];
$result = 'mysql:';
if (isset($dsn['database'])) {
$params[] = 'dbname=' . $dsn['database'];
}
if (isset($dsn['hostspec'])) {
$params[] = 'host=' . $dsn['hostspec'];
}
if (isset($dsn['port'])) {
$params[] = 'port=' . $dsn['port'];
}
if (isset($dsn['socket'])) {
$params[] = 'unix_socket=' . $dsn['socket'];
}
$params[] = 'charset=' . (!empty($dsn['charset']) ? $dsn['charset'] : 'utf8mb4');
if (!empty($params)) {
$result .= implode(';', $params);
}
return $result;
}
/**
* Returns driver-specific connection options
*
* @param array $dsn DSN parameters
*
* @return array Connection options
*/
protected function dsn_options($dsn)
{
$result = parent::dsn_options($dsn);
if (!empty($dsn['key'])) {
$result[PDO::MYSQL_ATTR_SSL_KEY] = $dsn['key'];
}
if (!empty($dsn['cipher'])) {
$result[PDO::MYSQL_ATTR_SSL_CIPHER] = $dsn['cipher'];
}
if (!empty($dsn['cert'])) {
$result[PDO::MYSQL_ATTR_SSL_CERT] = $dsn['cert'];
}
if (!empty($dsn['capath'])) {
$result[PDO::MYSQL_ATTR_SSL_CAPATH] = $dsn['capath'];
}
if (!empty($dsn['ca'])) {
$result[PDO::MYSQL_ATTR_SSL_CA] = $dsn['ca'];
}
if (isset($dsn['verify_server_cert'])) {
$result[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = rcube_utils::get_boolean($dsn['verify_server_cert']);
}
// Always return matching (not affected only) rows count
$result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
// Enable AUTOCOMMIT mode (#1488902)
$result[PDO::ATTR_AUTOCOMMIT] = true;
// Disable emulating of prepared statements
$result[PDO::ATTR_EMULATE_PREPARES] = false;
return $result;
}
/**
* Returns list of tables in a database
*
* @return array List of all tables of the current database
*/
public function list_tables()
{
// get tables if not cached
if ($this->tables === null) {
$q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"
. " WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'"
. " ORDER BY TABLE_NAME", $this->db_dsnw_array['database']);
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : [];
}
return $this->tables;
}
/**
* Returns list of columns in database table
*
* @param string $table Table name
*
* @return array List of table cols
*/
public function list_cols($table)
{
$q = $this->query("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS"
. " WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?",
$this->db_dsnw_array['database'], $table);
if ($q) {
return $q->fetchAll(PDO::FETCH_COLUMN, 0);
}
return [];
}
/**
* Get database runtime variables
*
* @param string $varname Variable name
* @param mixed $default Default value if variable is not set
*
* @return mixed Variable value or default
*/
public function get_variable($varname, $default = null)
{
if (!isset($this->variables)) {
$this->variables = [];
}
if (array_key_exists($varname, $this->variables)) {
return $this->variables[$varname];
}
// configured value has higher prio
$conf_value = rcube::get_instance()->config->get('db_' . $varname);
if ($conf_value !== null) {
return $this->variables[$varname] = $conf_value;
}
$result = $this->query('SHOW VARIABLES LIKE ?', $varname);
while ($row = $this->fetch_array($result)) {
$this->variables[$row[0]] = $row[1];
}
// not found, use default
if (!isset($this->variables[$varname])) {
$this->variables[$varname] = $default;
}
return $this->variables[$varname];
}
/**
* INSERT ... ON DUPLICATE KEY UPDATE (or equivalent).
* When not supported by the engine we do UPDATE and INSERT.
*
* @param string $table Table name (should be already passed via table_name() with quoting)
* @param array $keys Hash array (column => value) of the unique constraint
* @param array $columns List of columns to update
* @param array $values List of values to update (number of elements
* should be the same as in $columns)
*
* @return PDOStatement|bool Query handle or False on error
* @todo Multi-insert support
*/
public function insert_or_update($table, $keys, $columns, $values)
{
$columns = array_map(function($i) { return "`$i`"; }, $columns);
$cols = implode(', ', array_map(function($i) { return "`$i`"; }, array_keys($keys)));
$cols .= ', ' . implode(', ', $columns);
$vals = implode(', ', array_map(function($i) { return $this->quote($i); }, $keys));
$vals .= ', ' . rtrim(str_repeat('?, ', count($columns)), ', ');
$update = implode(', ', array_map(function($i) { return "$i = VALUES($i)"; }, $columns));
return $this->query("INSERT INTO $table ($cols) VALUES ($vals)"
. " ON DUPLICATE KEY UPDATE $update", $values);
}
}
@@ -0,0 +1,631 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class that implements database functions |
| for Oracle database using OCI8 extension |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface
*
* @package Framework
* @subpackage Database
*/
class rcube_db_oracle extends rcube_db
{
public $db_provider = 'oracle';
public $in_transaction = false;
/**
* Create connection instance
*/
protected function conn_create($dsn)
{
// Get database specific connection options
$dsn_options = $this->dsn_options($dsn);
$function = $this->db_pconn ? 'oci_pconnect' : 'oci_connect';
if (!function_exists($function)) {
$this->db_error = true;
$this->db_error_msg = 'OCI8 extension not loaded. See http://php.net/manual/en/book.oci8.php';
rcube::raise_error([
'code' => 500, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => $this->db_error_msg
], true, false);
return;
}
// connect
$dbh = @$function($dsn['username'], $dsn['password'], $dsn_options['database'], $dsn_options['charset']);
if (!$dbh) {
$error = oci_error();
$this->db_error = true;
$this->db_error_msg = $error['message'];
rcube::raise_error([
'code' => 500, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => $this->db_error_msg
], true, false);
return;
}
// configure session
$this->conn_configure($dsn, $dbh);
return $dbh;
}
/**
* Driver-specific configuration of database connection
*
* @param array $dsn DSN for DB connections
* @param PDO $dbh Connection handler
*/
protected function conn_configure($dsn, $dbh)
{
$init_queries = [
"ALTER SESSION SET nls_date_format = 'YYYY-MM-DD'",
"ALTER SESSION SET nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'",
];
foreach ($init_queries as $query) {
$stmt = oci_parse($dbh, $query);
oci_execute($stmt);
}
}
/**
* Connection state checker
*
* @return bool True if in connected state
*/
public function is_connected()
{
return empty($this->dbh) ? false : $this->db_connected;
}
/**
* Execute a SQL query with limits
*
* @param string $query SQL query to execute
* @param int $offset Offset for LIMIT statement
* @param int $numrows Number of rows for LIMIT statement
* @param array $params Values to be inserted in query
*
* @return PDOStatement|bool Query handle or False on error
*/
protected function _query($query, $offset, $numrows, $params)
{
$query = ltrim($query);
$this->db_connect($this->dsn_select($query), true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
if ($numrows || $offset) {
$query = $this->set_limit($query, $numrows, $offset);
}
// replace self::DEFAULT_QUOTE with driver-specific quoting
$query = $this->query_parse($query);
// Because in Roundcube we mostly use queries that are
// executed only once, we will not use prepared queries
$pos = 0;
$idx = 0;
$args = [];
if (!empty($params)) {
while ($pos = strpos($query, '?', $pos)) {
if ($query[$pos+1] == '?') { // skip escaped '?'
$pos += 2;
}
else {
$val = $this->quote($params[$idx++]);
// long strings are not allowed inline, need to be parametrized
if (strlen($val) > 4000) {
$key = ':param' . (count($args) + 1);
$args[$key] = $params[$idx-1];
$val = $key;
}
unset($params[$idx-1]);
$query = substr_replace($query, $val, $pos, 1);
$pos += strlen($val);
}
}
}
$query = rtrim($query, " \t\n\r\0\x0B;");
// replace escaped '?' and quotes back to normal, see self::quote()
$query = str_replace(
['??', self::DEFAULT_QUOTE.self::DEFAULT_QUOTE],
['?', self::DEFAULT_QUOTE],
$query
);
// log query
$this->debug($query);
// destroy reference to previous result
$this->last_result = null;
$this->db_error_msg = null;
// prepare query
$result = @oci_parse($this->dbh, $query);
$mode = $this->in_transaction ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
if ($result) {
foreach (array_keys($args) as $param) {
oci_bind_by_name($result, $param, $args[$param], -1, SQLT_LNG);
}
}
// execute query
if (!$result || !@oci_execute($result, $mode)) {
$result = $this->handle_error($query, $result);
}
return $this->last_result = $result;
}
/**
* Helper method to handle DB errors.
* This by default logs the error but could be overridden by a driver implementation
*
* @param string Query that triggered the error
* @param resource Query result
*
* @return bool Result to be stored and returned
*/
protected function handle_error($query, $result = null)
{
$error = oci_error($result ?: $this->dbh);
// @TODO: Find error codes for key errors
if (empty($this->options['ignore_key_errors']) || !in_array($error['code'], ['23000', '23505'])) {
$this->db_error = true;
$this->db_error_msg = sprintf('[%s] %s', $error['code'], $error['message']);
rcube::raise_error([
'code' => 500, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => $this->db_error_msg . " (SQL Query: $query)"
], true, false);
}
return false;
}
/**
* Get last inserted record ID
*
* @param string $table Table name (to find the incremented sequence)
*
* @return mixed ID or false on failure
*/
public function insert_id($table = null)
{
if (!$this->db_connected || $this->db_mode == 'r' || empty($table)) {
return false;
}
$sequence = $this->quote_identifier($this->sequence_name($table));
$result = $this->query("SELECT $sequence.currval FROM dual");
$result = $this->fetch_array($result);
return !empty($result[0]) ? $result[0] : false;
}
/**
* Get number of affected rows for the last query
*
* @param mixed $result Optional query handle
*
* @return int Number of (matching) rows
*/
public function affected_rows($result = null)
{
if ($result || ($result === null && ($result = $this->last_result))) {
return oci_num_rows($result);
}
return 0;
}
/**
* Get number of rows for a SQL query
* If no query handle is specified, the last query will be taken as reference
*
* @param mixed $result Optional query handle
*
* @return mixed Number of rows or false on failure
* @deprecated This method shows very poor performance and should be avoided.
*/
public function num_rows($result = null)
{
// not implemented
return false;
}
/**
* Get an associative array for one row
* If no query handle is specified, the last query will be taken as reference
*
* @param mixed $result Optional query handle
*
* @return mixed Array with col values or false on failure
*/
public function fetch_assoc($result = null)
{
return $this->_fetch_row($result, OCI_ASSOC);
}
/**
* Get an index array for one row
* If no query handle is specified, the last query will be taken as reference
*
* @param mixed $result Optional query handle
*
* @return mixed Array with col values or false on failure
*/
public function fetch_array($result = null)
{
return $this->_fetch_row($result, OCI_NUM);
}
/**
* Get col values for a result row
*
* @param mixed $result Optional query handle
* @param int $mode Fetch mode identifier
*
* @return array|false Array with col values or false on failure
*/
protected function _fetch_row($result, $mode)
{
if ($result || ($result === null && ($result = $this->last_result))) {
return oci_fetch_array($result, $mode + OCI_RETURN_NULLS + OCI_RETURN_LOBS);
}
return false;
}
/**
* Formats input so it can be safely used in a query
* PDO_OCI does not implement quote() method
*
* @param mixed $input Value to quote
* @param string $type Type of data (integer, bool, ident)
*
* @return string Quoted/converted string for use in query
*/
public function quote($input, $type = null)
{
// handle int directly for better performance
if ($type == 'integer' || $type == 'int') {
return intval($input);
}
if (is_null($input)) {
return 'NULL';
}
if ($input instanceof DateTime) {
return $this->quote($input->format($this->options['datetime_format']));
}
if ($type == 'ident') {
return $this->quote_identifier($input);
}
switch ($type) {
case 'bool':
case 'integer':
return intval($input);
default:
return "'" . strtr($input, [
'?' => '??',
"'" => "''",
rcube_db::DEFAULT_QUOTE => rcube_db::DEFAULT_QUOTE . rcube_db::DEFAULT_QUOTE
]) . "'";
}
}
/**
* Return correct name for a specific database sequence
*
* @param string $table Table name
*
* @return string Translated sequence name
*/
protected function sequence_name($table)
{
// Note: we support only one sequence per table
// Note: The sequence name must be <table_name>_seq
$sequence = $table . '_seq';
// modify sequence name if prefix is configured
if ($prefix = $this->options['table_prefix']) {
return $prefix . $sequence;
}
return $sequence;
}
/**
* Return SQL statement for case insensitive LIKE
*
* @param string $column Field name
* @param string $value Search value
*
* @return string SQL statement to use in query
*/
public function ilike($column, $value)
{
return 'UPPER(' . $this->quote_identifier($column) . ') LIKE UPPER(' . $this->quote($value) . ')';
}
/**
* Return SQL function for current time and date
*
* @param int $interval Optional interval (in seconds) to add/subtract
*
* @return string SQL function to use in query
*/
public function now($interval = 0)
{
if ($interval) {
$interval = intval($interval);
return "current_timestamp + INTERVAL '$interval' SECOND";
}
return "current_timestamp";
}
/**
* Return SQL statement to convert a field value into a unix timestamp
*
* @param string $field Field name
*
* @return string SQL statement to use in query
* @deprecated
*/
public function unixtimestamp($field)
{
return "(($field - to_date('1970-01-01','YYYY-MM-DD')) * 60 * 60 * 24)";
}
/**
* Adds TOP (LIMIT,OFFSET) clause to the query
*
* @param string $query SQL query
* @param int $limit Number of rows
* @param int $offset Offset
*
* @return string SQL query
*/
protected function set_limit($query, $limit = 0, $offset = 0)
{
$limit = intval($limit);
$offset = intval($offset);
$end = $offset + $limit;
// @TODO: Oracle 12g has better OFFSET support
if (!$offset) {
$query = "SELECT * FROM ($query) a WHERE rownum <= $end";
}
else {
$query = "SELECT * FROM (SELECT a.*, rownum as rn FROM ($query) a WHERE rownum <= $end) b WHERE rn > $offset";
}
return $query;
}
/**
* Parse SQL file and fix table names according to table prefix
*/
protected function fix_table_names($sql)
{
if (!$this->options['table_prefix']) {
return $sql;
}
$sql = parent::fix_table_names($sql);
// replace sequence names, and other Oracle-specific commands
$sql = preg_replace_callback('/(SEQUENCE ["]?)([^" \r\n]+)/',
[$this, 'fix_table_names_callback'],
$sql
);
$sql = preg_replace_callback(
'/([ \r\n]+["]?)([^"\' \r\n\.]+)(["]?\.nextval)/',
[$this, 'fix_table_names_seq_callback'],
$sql
);
return $sql;
}
/**
* Preg_replace callback for fix_table_names()
*/
protected function fix_table_names_seq_callback($matches)
{
return $matches[1] . $this->options['table_prefix'] . $matches[2] . $matches[3];
}
/**
* Returns connection options from DSN array
*/
protected function dsn_options($dsn)
{
$params = [];
if (isset($dsn['hostspec'])) {
$host = $dsn['hostspec'];
if (isset($dsn['port'])) {
$host .= ':' . $dsn['port'];
}
$params['database'] = $host . '/' . $dsn['database'];
}
$params['charset'] = 'UTF8';
return $params;
}
/**
* Execute the given SQL script
*
* @param string $sql SQL queries to execute
*
* @return bool True on success, False on error
*/
public function exec_script($sql)
{
$sql = $this->fix_table_names($sql);
$buff = '';
$body = false;
foreach (explode("\n", $sql) as $line) {
$tok = strtolower(trim($line));
if (preg_match('/^--/', $line) || $tok == '' || $tok == '/') {
continue;
}
$buff .= $line . "\n";
// detect PL/SQL function bodies, don't break on semicolon
if ($body && $tok == 'end;') {
$body = false;
}
else if (!$body && $tok == 'begin') {
$body = true;
}
if (!$body && substr($tok, -1) == ';') {
$this->query($buff);
$buff = '';
if ($this->db_error) {
break;
}
}
}
return !$this->db_error;
}
/**
* Start transaction
*
* @return bool True on success, False on failure
*/
public function startTransaction()
{
$this->db_connect('w', true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
$this->debug('BEGIN TRANSACTION');
return $this->last_result = $this->in_transaction = true;
}
/**
* Commit transaction
*
* @return bool True on success, False on failure
*/
public function endTransaction()
{
$this->db_connect('w', true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
$this->debug('COMMIT TRANSACTION');
if ($result = @oci_commit($this->dbh)) {
$this->in_transaction = true;
}
else {
$this->handle_error('COMMIT');
}
return $this->last_result = $result;
}
/**
* Rollback transaction
*
* @return bool True on success, False on failure
*/
public function rollbackTransaction()
{
$this->db_connect('w', true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
$this->debug('ROLLBACK TRANSACTION');
if (@oci_rollback($this->dbh)) {
$this->in_transaction = false;
}
else {
$this->handle_error('ROLLBACK');
}
return $this->last_result = $this->dbh->rollBack();
}
/**
* Terminate database connection.
*/
public function closeConnection()
{
// release statement and close connection(s)
$this->last_result = null;
foreach ($this->dbhs as $dbh) {
oci_close($dbh);
}
parent::closeConnection();
}
}
@@ -0,0 +1,58 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class for query parameters |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database query parameter
*
* @package Framework
* @subpackage Database
*/
class rcube_db_param
{
protected $db;
protected $type;
protected $value;
/**
* Object constructor
*
* @param rcube_db $db Database driver
* @param mixed $value Parameter value
* @param string $type Parameter type (One of rcube_db::TYPE_* constants)
*/
public function __construct($db, $value, $type = null)
{
$this->db = $db;
$this->value = $value;
$this->type = $type;
}
/**
* Returns the value as string for inlining into SQL query
*/
public function __toString()
{
if ($this->type === rcube_db::TYPE_SQL) {
return (string) $this->value;
}
return (string) $this->db->quote($this->value, $this->type);
}
}
@@ -0,0 +1,336 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for PostgreSQL database |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
*
* @package Framework
* @subpackage Database
*/
class rcube_db_pgsql extends rcube_db
{
public $db_provider = 'postgres';
// See https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS
private static $libpq_connect_params = [
'application_name',
'sslmode',
'sslcert',
'sslkey',
'sslrootcert',
'sslcrl',
'sslcompression',
'service'
];
/**
* {@inheritdoc}
*/
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
{
parent::__construct($db_dsnw, $db_dsnr, $pconn);
// use date/time input format with timezone spec.
$this->options['datetime_format'] = 'c';
}
/**
* Driver-specific configuration of database connection
*
* @param array $dsn DSN for DB connections
* @param PDO $dbh Connection handler
*/
protected function conn_configure($dsn, $dbh)
{
$dbh->query("SET NAMES 'utf8'");
$dbh->query("SET DATESTYLE TO ISO");
// if ?schema= is set in dsn, set the search_path
if (!empty($dsn['schema'])) {
$dbh->query("SET search_path TO " . $this->quote($dsn['schema']));
}
}
/**
* Get last inserted record ID
*
* @param string $table Table name (to find the incremented sequence)
*
* @return mixed ID or false on failure
*/
public function insert_id($table = null)
{
if (!$this->db_connected || $this->db_mode == 'r') {
return false;
}
if ($table) {
$table = $this->sequence_name($table);
}
return $this->dbh->lastInsertId($table);
}
/**
* Return correct name for a specific database sequence
*
* @param string $table Table name
*
* @return string Translated sequence name
*/
protected function sequence_name($table)
{
// Note: we support only one sequence per table
// Note: The sequence name must be <table_name>_seq
$sequence = $table . '_seq';
// modify sequence name if prefix is configured
if ($prefix = $this->options['table_prefix']) {
return $prefix . $sequence;
}
return $sequence;
}
/**
* Return SQL statement to convert a field value into a unix timestamp
*
* @param string $field Field name
*
* @return string SQL statement to use in query
* @deprecated
*/
public function unixtimestamp($field)
{
return "EXTRACT (EPOCH FROM $field)";
}
/**
* Return SQL function for current time and date
*
* @param int $interval Optional interval (in seconds) to add/subtract
*
* @return string SQL function to use in query
*/
public function now($interval = 0)
{
$result = 'now()';
if ($interval) {
$result .= ' ' . ($interval > 0 ? '+' : '-') . " interval '"
. ($interval > 0 ? intval($interval) : intval($interval) * -1)
. " seconds'";
}
return $result;
}
/**
* Return SQL statement for case insensitive LIKE
*
* @param string $column Field name
* @param string $value Search value
*
* @return string SQL statement to use in query
*/
public function ilike($column, $value)
{
return $this->quote_identifier($column) . ' ILIKE ' . $this->quote($value);
}
/**
* Get database runtime variables
*
* @param string $varname Variable name
* @param mixed $default Default value if variable is not set
*
* @return mixed Variable value or default
*/
public function get_variable($varname, $default = null)
{
// There's a known case when max_allowed_packet is queried
// PostgreSQL doesn't have such limit, return immediately
if ($varname == 'max_allowed_packet') {
return rcube::get_instance()->config->get('db_' . $varname, $default);
}
$this->variables[$varname] = rcube::get_instance()->config->get('db_' . $varname);
if (!isset($this->variables)) {
$this->variables = [];
$result = $this->query('SHOW ALL');
while ($row = $this->fetch_array($result)) {
$this->variables[$row[0]] = $row[1];
}
}
return $this->variables[$varname] ?? $default;
}
/**
* INSERT ... ON CONFLICT DO UPDATE.
* When not supported by the engine we do UPDATE and INSERT.
*
* @param string $table Table name (should be already passed via table_name() with quoting)
* @param array $keys Hash array (column => value) of the unique constraint
* @param array $columns List of columns to update
* @param array $values List of values to update (number of elements
* should be the same as in $columns)
*
* @return PDOStatement|bool Query handle or False on error
* @todo Multi-insert support
*/
public function insert_or_update($table, $keys, $columns, $values)
{
// Check if version >= 9.5, otherwise use fallback
if ($this->get_variable('server_version_num') < 90500) {
return parent::insert_or_update($table, $keys, $columns, $values);
}
$columns = array_map([$this, 'quote_identifier'], $columns);
$target = implode(', ', array_map([$this, 'quote_identifier'], array_keys($keys)));
$cols = $target . ', ' . implode(', ', $columns);
$vals = implode(', ', array_map(function($i) { return $this->quote($i); }, $keys));
$vals .= ', ' . rtrim(str_repeat('?, ', count($columns)), ', ');
$update = implode(', ', array_map(function($i) { return "$i = EXCLUDED.$i"; }, $columns));
return $this->query("INSERT INTO $table ($cols) VALUES ($vals)"
. " ON CONFLICT ($target) DO UPDATE SET $update", $values);
}
/**
* Returns list of tables in a database
*
* @return array List of all tables of the current database
*/
public function list_tables()
{
// get tables if not cached
if ($this->tables === null) {
if (($schema = $this->options['table_prefix']) && $schema[strlen($schema)-1] === '.') {
$add = " AND TABLE_SCHEMA = " . $this->quote(substr($schema, 0, -1));
}
else {
$add = " AND TABLE_SCHEMA NOT IN ('pg_catalog', 'information_schema')";
}
$q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"
. " WHERE TABLE_TYPE = 'BASE TABLE'" . $add
. " ORDER BY TABLE_NAME");
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : [];
}
return $this->tables;
}
/**
* Returns list of columns in database table
*
* @param string $table Table name
*
* @return array List of table cols
*/
public function list_cols($table)
{
$args = [$table];
if (($schema = $this->options['table_prefix']) && $schema[strlen($schema)-1] === '.') {
$add = " AND TABLE_SCHEMA = ?";
$args[] = substr($schema, 0, -1);
}
else {
$add = " AND TABLE_SCHEMA NOT IN ('pg_catalog', 'information_schema')";
}
$q = $this->query("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS"
. " WHERE TABLE_NAME = ?" . $add, $args);
if ($q) {
return $q->fetchAll(PDO::FETCH_COLUMN, 0);
}
return [];
}
/**
* Returns PDO DSN string from DSN array
*
* @param array $dsn DSN parameters
*
* @return string DSN string
*/
protected function dsn_string($dsn)
{
$params = [];
$result = 'pgsql:';
if (isset($dsn['hostspec'])) {
$params[] = 'host=' . $dsn['hostspec'];
}
else if (isset($dsn['socket'])) {
$params[] = 'host=' . $dsn['socket'];
}
if (isset($dsn['port'])) {
$params[] = 'port=' . $dsn['port'];
}
if (isset($dsn['database'])) {
$params[] = 'dbname=' . $dsn['database'];
}
foreach (self::$libpq_connect_params as $param) {
if (isset($dsn[$param])) {
$params[] = $param . '=' . $dsn[$param];
}
}
if (!empty($params)) {
$result .= implode(';', $params);
}
return $result;
}
/**
* Parse SQL file and fix table names according to table prefix
*/
protected function fix_table_names($sql)
{
if (!$this->options['table_prefix']) {
return $sql;
}
$sql = parent::fix_table_names($sql);
// replace sequence names, and other postgres-specific commands
$sql = preg_replace_callback(
'/((SEQUENCE |RENAME TO |nextval\()["\']*)([^"\' \r\n]+)/',
[$this, 'fix_table_names_callback'],
$sql
);
return $sql;
}
}
@@ -0,0 +1,171 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for SQLite database |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
*
* @package Framework
* @subpackage Database
*/
class rcube_db_sqlite extends rcube_db
{
public $db_provider = 'sqlite';
/**
* Prepare connection
*/
protected function conn_prepare($dsn)
{
// Create database file, required by PDO to exist on connection
if (!empty($dsn['database']) && !file_exists($dsn['database'])) {
$created = touch($dsn['database']);
// File mode setting, for compat. with MDB2
if (!empty($dsn['mode']) && $created) {
chmod($dsn['database'], octdec($dsn['mode']));
}
}
}
/**
* Configure connection, create database if not exists
*/
protected function conn_configure($dsn, $dbh)
{
// Initialize database structure in file is empty
if (!empty($dsn['database']) && !filesize($dsn['database'])) {
$data = file_get_contents(RCUBE_INSTALL_PATH . 'SQL/sqlite.initial.sql');
if (strlen($data)) {
$this->debug('INITIALIZE DATABASE');
$q = $dbh->exec($data);
if ($q === false) {
$error = $dbh->errorInfo();
$this->db_error = true;
$this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
rcube::raise_error([
'code' => 500, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => $this->db_error_msg
],
true, false
);
}
}
}
// Enable WAL mode to fix locking issues like #8035.
$dbh->query("PRAGMA journal_mode = WAL");
// Enable foreign keys (requires sqlite 3.6.19 compiled with FK support)
$dbh->query("PRAGMA foreign_keys = ON");
}
/**
* Return SQL statement to convert a field value into a unix timestamp
*
* @param string $field Field name
*
* @return string SQL statement to use in query
* @deprecated
*/
public function unixtimestamp($field)
{
return "strftime('%s', $field)";
}
/**
* Return SQL function for current time and date
*
* @param int $interval Optional interval (in seconds) to add/subtract
*
* @return string SQL function to use in query
*/
public function now($interval = 0)
{
$add = '';
if ($interval) {
$add = ($interval > 0 ? '+' : '') . intval($interval) . ' seconds';
}
return "datetime('now'" . ($add ? ", '$add'" : "") . ")";
}
/**
* Returns list of tables in database
*
* @return array List of all tables of the current database
*/
public function list_tables()
{
if ($this->tables === null) {
$q = $this->query('SELECT name FROM sqlite_master'
.' WHERE type = \'table\' ORDER BY name');
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : [];
}
return $this->tables;
}
/**
* Returns list of columns in database table
*
* @param string $table Table name
*
* @return array List of table cols
*/
public function list_cols($table)
{
$q = $this->query('PRAGMA table_info(?)', $table);
return $q ? $q->fetchAll(PDO::FETCH_COLUMN, 1) : [];
}
/**
* Build DSN string for PDO constructor
*/
protected function dsn_string($dsn)
{
return $dsn['phptype'] . ':' . $dsn['database'];
}
/**
* Returns driver-specific connection options
*
* @param array $dsn DSN parameters
*
* @return array Connection options
*/
protected function dsn_options($dsn)
{
$result = parent::dsn_options($dsn);
// Change the default timeout (60) to a smaller value
$result[PDO::ATTR_TIMEOUT] = isset($dsn['timeout']) ? intval($dsn['timeout']) : 10;
return $result;
}
}
@@ -0,0 +1,101 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Database wrapper class that implements PHP PDO functions |
| for MS SQL Server database |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface
* This is a wrapper for the PHP PDO
*
* @package Framework
* @subpackage Database
*/
class rcube_db_sqlsrv extends rcube_db_mssql
{
/**
* Get last inserted record ID
*
* @param string $table Table name (to find the incremented sequence)
*
* @return string|false The ID or False on failure
*/
public function insert_id($table = '')
{
if (!$this->db_connected || $this->db_mode == 'r') {
return false;
}
if ($table) {
// For some unknown reason the constant described in the driver docs
// might not exist, we'll fallback to PDO::ATTR_CLIENT_VERSION (#7564)
if (defined('PDO::ATTR_DRIVER_VERSION')) {
$driver_version = $this->dbh->getAttribute(PDO::ATTR_DRIVER_VERSION);
}
else if (defined('PDO::ATTR_CLIENT_VERSION')) {
$client_version = $this->dbh->getAttribute(PDO::ATTR_CLIENT_VERSION);
$driver_version = $client_version['ExtensionVer'];
}
else {
$driver_version = 5;
}
// Starting from version 5 of the driver lastInsertId() method expects
// a sequence name instead of a table name. We'll unset the argument
// to get the last insert sequence (#7564)
if (version_compare($driver_version, '5', '>=')) {
$table = null;
}
else {
// resolve table name
$table = $this->table_name($table);
}
}
return $this->dbh->lastInsertId($table);
}
/**
* Returns PDO DSN string from DSN array
*/
protected function dsn_string($dsn)
{
$params = [];
$result = 'sqlsrv:';
if (isset($dsn['hostspec'])) {
$host = $dsn['hostspec'];
if (isset($dsn['port'])) {
$host .= ',' . $dsn['port'];
}
$params[] = 'Server=' . $host;
}
if (isset($dsn['database'])) {
$params[] = 'Database=' . $dsn['database'];
}
if (!empty($params)) {
$result .= implode(';', $params);
}
return $result;
}
}