462 lines
11 KiB
PHP
462 lines
11 KiB
PHP
<?php
|
|
namespace XFramework;
|
|
|
|
/**
|
|
* Roundcube Plus Framework plugin.
|
|
*
|
|
* This class provides shortcut functions to the Roundcube database access.
|
|
*
|
|
* Copyright 2016, Tecorama LLC.
|
|
*
|
|
* @license Commercial. See the LICENSE file for details.
|
|
*/
|
|
|
|
defined("BOOL") || define("BOOL", "bool");
|
|
defined("INT") || define("INT", "int");
|
|
|
|
abstract class DatabaseGeneric
|
|
{
|
|
const BOOL = "bool";
|
|
const INT = "int";
|
|
protected $prefix;
|
|
protected $db;
|
|
|
|
/**
|
|
* Database constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$rcmail = xrc();
|
|
$this->db = $rcmail->get_dbh();
|
|
$this->prefix = $rcmail->config->get("db_prefix");
|
|
}
|
|
|
|
/**
|
|
* Returns the db provider.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getProvider(): string
|
|
{
|
|
return $this->db->db_provider;
|
|
}
|
|
|
|
/**
|
|
* Quotes the column name that is the same as a keyword. This is different in different db types, the standard
|
|
* is a double quote (used in postgres & sqlite) but for example mysql uses backticks.
|
|
*
|
|
* @param string $string
|
|
* @return string
|
|
*/
|
|
public function col(string $string): string
|
|
{
|
|
return $this->db->quote_identifier($string);
|
|
}
|
|
|
|
/**
|
|
* Convert bool or int values into actual bool or int values. (PDO returns int and bool as strings, which later
|
|
* causes problems when the values are sent to javascript.)
|
|
*
|
|
* @param array $data
|
|
* @param $type
|
|
* @param array $names
|
|
*/
|
|
public function fix(array &$data, $type, array $names)
|
|
{
|
|
foreach ($names as $name) {
|
|
if ($type == BOOL) {
|
|
$data[$name] = (bool)$data[$name];
|
|
} else if ($type == INT) {
|
|
$data[$name] = (int)$data[$name];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the last insert id.
|
|
*
|
|
* @param string $table
|
|
* @return mixed
|
|
*/
|
|
public function lastInsertId(string $table)
|
|
{
|
|
return $this->db->insert_id($table);
|
|
}
|
|
|
|
/**
|
|
* Returns the last error message.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function lastError(): string
|
|
{
|
|
return $this->db->is_error();
|
|
}
|
|
|
|
/**
|
|
* Begins a transaction.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function beginTransaction(): bool
|
|
{
|
|
return $this->db->startTransaction();
|
|
}
|
|
|
|
/**
|
|
* Commits a transaction.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function commit(): bool
|
|
{
|
|
return $this->db->endTransaction();
|
|
}
|
|
|
|
/**
|
|
* Rolls back a transaction.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function rollBack(): bool
|
|
{
|
|
return $this->db->rollbackTransaction();
|
|
}
|
|
|
|
/**
|
|
* Fetches the query result.
|
|
*
|
|
* @param string $query
|
|
* @param mixed $parameters
|
|
* @return array|bool
|
|
*/
|
|
public function fetch(string $query, $parameters = [])
|
|
{
|
|
if (!($statement = $this->query($query, $parameters))) {
|
|
return false;
|
|
}
|
|
|
|
return $statement->fetch(\PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a single row from the database.
|
|
*
|
|
* @param string $table
|
|
* @param array $whereParams
|
|
* @return array|bool
|
|
*/
|
|
public function row(string $table, array $whereParams)
|
|
{
|
|
$this->createWhereParams($whereParams, $where, $param);
|
|
return $this->fetch("SELECT * FROM {" .$table . "} WHERE $where LIMIT 1", $param);
|
|
}
|
|
|
|
/**
|
|
* Returns record count.
|
|
*
|
|
* @param string $table
|
|
* @param array $whereParams
|
|
* @return string|null
|
|
*/
|
|
public function count(string $table, array $whereParams): ?string
|
|
{
|
|
return $this->value("COUNT(*)", $table, $whereParams);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a single value from the database.
|
|
*
|
|
* @param string $field
|
|
* @param string $table
|
|
* @param array $whereParams
|
|
* @return string|null
|
|
*/
|
|
public function value(string $field, string $table, array $whereParams): ?string
|
|
{
|
|
$this->createWhereParams($whereParams, $where, $param);
|
|
if (!($row = $this->fetch("SELECT $field FROM {" .$table . "} WHERE $where LIMIT 1", $param))) {
|
|
return null;
|
|
}
|
|
|
|
if ($field == "COUNT(*)" && !array_key_exists($field, $row) && array_key_exists("count", $row)) {
|
|
// @codeCoverageIgnoreStart
|
|
$field = "count";
|
|
// @codeCoverageIgnoreEnd
|
|
}
|
|
|
|
return array_key_exists($field, $row) ? $row[$field] : null;
|
|
}
|
|
|
|
/**
|
|
* Retrieve multiple rows from the database as associate array.
|
|
*
|
|
* @param string $query
|
|
* @param string|array $parameters
|
|
* @param string $resultKeyField
|
|
* @return array|boolean
|
|
*/
|
|
public function all(string $query, $parameters = [], string $resultKeyField = "")
|
|
{
|
|
if (!($statement = $this->query($query, $parameters))) {
|
|
return false;
|
|
}
|
|
|
|
$array = $statement->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// if $resultKeyField specified, place the requested field as the resulting array key
|
|
if (!empty($array) && $resultKeyField) {
|
|
$result = [];
|
|
foreach ($array as $item) {
|
|
$result[$item[$resultKeyField]] = $item;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Inserts a record into the database.
|
|
*
|
|
* @param string $table
|
|
* @param array $data
|
|
* @return bool
|
|
*/
|
|
public function insert(string $table, array $data): bool
|
|
{
|
|
$data = $this->fixWriteData($data);
|
|
$fields = [];
|
|
$markers = [];
|
|
$values = [];
|
|
|
|
foreach ($data as $field => $value) {
|
|
$fields[] = "`$field`";
|
|
$markers[] = "?";
|
|
$values[] = $value;
|
|
}
|
|
|
|
$fields = implode(",", $fields);
|
|
$markers = implode(",", $markers);
|
|
|
|
if (!$this->query("INSERT INTO {" . $table . "} ($fields) VALUES ($markers)", $values)) {
|
|
$this->logLastError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Logs last query error to the Roundcube error log.
|
|
*
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function logLastError()
|
|
{
|
|
if (class_exists("\\rcube")) {
|
|
\rcube::write_log('errors', $this->lastError());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates records in a table.
|
|
*
|
|
* @param string $table
|
|
* @param array $data
|
|
* @param array $whereParams
|
|
* @return bool
|
|
*/
|
|
public function update(string $table, array $data, array $whereParams): bool
|
|
{
|
|
$data = $this->fixWriteData($data);
|
|
$fields = [];
|
|
$param = [];
|
|
$where = [];
|
|
|
|
foreach ($data as $key => $val) {
|
|
$fields[] = "`$key`=?";
|
|
$param[] = $val;
|
|
}
|
|
|
|
$this->createWhereParams($whereParams, $where, $param);
|
|
$fields = implode(",", $fields);
|
|
|
|
if (!$this->query("UPDATE {" . $table . "} SET $fields WHERE $where", $param)) {
|
|
$this->logLastError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Removes records from a table.
|
|
*
|
|
* @param string $table
|
|
* @param array $whereParams
|
|
* @param bool $addPrefix
|
|
* @return bool
|
|
*/
|
|
public function remove(string $table, array $whereParams, bool $addPrefix = true): bool
|
|
{
|
|
if ($addPrefix) {
|
|
$table = "{" . $table . "}";
|
|
}
|
|
|
|
$this->createWhereParams($whereParams, $where, $param);
|
|
|
|
if (!$this->query("DELETE FROM $table WHERE $where", $param)) {
|
|
$this->logLastError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Truncates a table.
|
|
*
|
|
* @param string $table
|
|
* @param bool $addPrefix
|
|
* @return bool
|
|
*/
|
|
public function truncate(string $table, bool $addPrefix = true): bool
|
|
{
|
|
if ($addPrefix) {
|
|
$table = "{" . $table . "}";
|
|
}
|
|
|
|
// we don't use truncate because of foreign key problems
|
|
if (!$this->query("DELETE FROM $table")) {
|
|
$this->logLastError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Run a database query. Returns PDO statement.
|
|
*
|
|
* @param string $query
|
|
* @param mixed $parameters
|
|
* @return \PDOStatement|bool
|
|
*/
|
|
public function query(string $query, $parameters = [])
|
|
{
|
|
return $this->db->query(
|
|
$this->prepareQuery($query),
|
|
is_array($parameters) ? $parameters : [$parameters]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the table name prefixed with the db_prefix config setting.
|
|
*
|
|
* @param string $table
|
|
* @param bool $quote
|
|
* @return string
|
|
*/
|
|
public function getTableName(string $table, bool $quote = true): string
|
|
{
|
|
$table = $this->prefix . $table;
|
|
return $quote ? $this->db->quote_identifier($table) : $table;
|
|
}
|
|
|
|
/**
|
|
* Replaces table names in queries enclosed in { } prefixing them with the db_prefix config setting.
|
|
*
|
|
* @param string $query
|
|
* @return string
|
|
*/
|
|
public function prepareQuery(string $query): string
|
|
{
|
|
return preg_replace_callback("/{([^}]+)}/", [$this, "pregQueryReplace"], $query);
|
|
}
|
|
|
|
/**
|
|
* Executes a query or a collection of queries. Executing a collection of queries using query() won't work in
|
|
* sqlite, only the first query will execute. Use this function instead.
|
|
*
|
|
* @param string $script
|
|
* @return bool
|
|
*/
|
|
public function script(string $script): bool
|
|
{
|
|
return $this->db->exec_script($script);
|
|
}
|
|
|
|
/**
|
|
* This function should be overwritten.
|
|
*
|
|
* @param string $table
|
|
* @param bool $addPrefix
|
|
* @return array
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function getColumns(string $table, bool $addPrefix = true): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @param string $column
|
|
* @param string $table
|
|
* @param bool $addPrefix
|
|
* @return bool
|
|
*/
|
|
public function hasColumn(string $column, string $table, bool $addPrefix = false): bool
|
|
{
|
|
return in_array($column, $this->getColumns($table, $addPrefix));
|
|
}
|
|
|
|
/**
|
|
* Fixes the data that is about to be written to database, for example, RC will try to write bool false as an
|
|
* empty string, which might cause problems with some databases.
|
|
*
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
private function fixWriteData(array $data): array
|
|
{
|
|
foreach ($data as $key => $val) {
|
|
if (is_bool($val)) {
|
|
$data[$key] = (int)$val;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param array $matches
|
|
* @return string
|
|
*/
|
|
protected function pregQueryReplace(array $matches): string
|
|
{
|
|
return " " . $this->getTableName($matches[1]) . " ";
|
|
}
|
|
|
|
/**
|
|
* @param array $whereParameters
|
|
* @param string|array $where
|
|
* @param string|array $param
|
|
*/
|
|
protected function createWhereParams(array $whereParameters, &$where, &$param)
|
|
{
|
|
is_array($where) || ($where = []);
|
|
is_array($param) || ($param = []);
|
|
|
|
foreach ($whereParameters as $key => $val) {
|
|
if ($val === null) {
|
|
$where[] = $this->col($key) . " IS NULL";
|
|
} else {
|
|
$where[] = $this->col($key) . "=?";
|
|
$param[] = $val;
|
|
}
|
|
}
|
|
|
|
$where = implode(" AND ", $where);
|
|
}
|
|
} |