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); } }