This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,139 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\WPDB\Exceptions\ConnectionException;
use MailPoet\Doctrine\WPDB\Exceptions\QueryException;
use MailPoetVendor\Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use mysqli;
use PDO;
use PDOException;
use Throwable;
use wpdb;
/**
* @phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
*/
class Connection implements ServerInfoAwareConnection {
public function __construct() {
global $wpdb;
if (!$wpdb instanceof wpdb) {
throw new ConnectionException('WPDB is not initialized.');
}
}
public function prepare(string $sql): Statement {
return new Statement($this, $sql);
}
public function query(string $sql): Result {
global $wpdb;
$value = $this->runQuery($sql);
$result = $wpdb->last_result;
return new Result($result, is_int($value) ? $value : 0);
}
public function exec(string $sql): int {
global $wpdb;
$this->runQuery($sql);
return $wpdb->rows_affected;
}
public function beginTransaction(): bool {
$this->runQuery('START TRANSACTION');
return true;
}
public function commit(): bool {
$this->runQuery('COMMIT');
return true;
}
public function rollBack(): bool {
$this->runQuery('ROLLBACK');
return true;
}
/**
* Quotes a string for use in a query.
* The type hint parameter is not needed for WPDB (mysqli).
* See also Doctrine\DBAL\Driver\Mysqli\Connection::quote().
*
* @param mixed $value
* @param int $type
*/
public function quote($value, $type = ParameterType::STRING): string {
global $wpdb;
return "'" . $wpdb->_escape($value) . "'";
}
/**
* @param string|null $name
*/
public function lastInsertId($name = null): int {
global $wpdb;
return $wpdb->insert_id;
}
public function getServerVersion(): string {
global $wpdb;
return $wpdb->db_server_info();
}
/**
* MySQL — returns an instance of mysqli.
* SQLite — returns an instance of PDO.
*
* @return mysqli|PDO|false|null
*/
public function getNativeConnection() {
global $wpdb;
// WPDB keeps connection instance (mysqli) in a protected property $dbh.
// We can access it using a closure that is bound to the $wpdb instance.
$getDbh = function () {
return $this->dbh; // @phpstan-ignore-line -- PHPStan doesn't know the binding context
};
$dbh = $getDbh->call($wpdb);
if (is_object($dbh) && method_exists($dbh, 'get_pdo')) {
return $dbh->get_pdo();
}
return $getDbh->call($wpdb);
}
public static function isSQLite(): bool {
return defined('DB_ENGINE') && DB_ENGINE === 'sqlite';
}
private function runQuery(string $sql) {
global $wpdb;
try {
$value = $wpdb->query($sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
} catch (Throwable $e) {
if ($e instanceof PDOException) {
throw new QueryException($e->getMessage(), $e->errorInfo[0] ?? null, $e->errorInfo[1] ?? 0);
}
throw new QueryException($e->getMessage(), null, 0, $e);
}
if ($value === false) {
$this->handleQueryError();
}
return $value;
}
private function handleQueryError(): void {
global $wpdb;
$nativeConnection = $this->getNativeConnection();
if ($nativeConnection instanceof mysqli) {
throw new QueryException($wpdb->last_error, $nativeConnection->sqlstate, $nativeConnection->errno);
} elseif ($nativeConnection instanceof PDO) {
$info = $nativeConnection->errorInfo();
throw new QueryException($wpdb->last_error, $info[0] ?? null, $info[1] ?? 0);
}
throw new QueryException($wpdb->last_error);
}
}
@@ -0,0 +1,87 @@
<?php declare (strict_types = 1);
namespace MailPoet\Doctrine\WPDB;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\WPDB\Exceptions\MissingParameterException;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use MailPoetVendor\Doctrine\DBAL\SQL\Parser\Visitor;
class ConvertParameters implements Visitor {
private const PARAM_TYPE_MAP = [
ParameterType::STRING => '%s',
ParameterType::INTEGER => '%d',
ParameterType::ASCII => '%s',
ParameterType::BINARY => '%s',
ParameterType::BOOLEAN => '%d',
ParameterType::NULL => '%s',
ParameterType::LARGE_OBJECT => '%s',
];
/** @var list<string> */
private array $buffer = [];
/** @var array<array-key, array{0: string, 1: mixed, 2: int}> */
private array $params;
private array $values = [];
private int $cursor = 1;
public function __construct(
array $params
) {
$this->params = $params;
}
public function acceptPositionalParameter(string $sql): void {
$position = $this->cursor++;
$this->acceptParameter($position);
}
public function acceptNamedParameter(string $sql): void {
$this->acceptParameter(trim($sql, ':'));
}
public function acceptOther(string $sql): void {
$this->buffer[] = $sql;
}
public function getSQL(): string {
return implode('', $this->buffer);
}
public function getValues(): array {
return $this->values;
}
/** @param array-key $key */
private function acceptParameter($key): void {
if (!array_key_exists($key, $this->params)) {
throw new MissingParameterException(sprintf("Parameter '%s' was defined in the query, but not provided.", $key));
}
[, $value, $type] = $this->params[$key];
// WPDB doesn't support NULL values. We need to handle them explicitly.
if ($value === null) {
$this->buffer[] = 'NULL';
return;
}
// WPDB doesn't accept non-scalar values. We need to cast them (PDO-like behavior).
if (!is_scalar($value)) {
if ($type === ParameterType::INTEGER) {
$value = (int)$value; // @phpstan-ignore-line -- cast may fail and that's OK
} elseif ($type === ParameterType::BOOLEAN) {
$value = (bool)$value;
} else {
$value = (string)$value; // @phpstan-ignore-line -- cast may fail and that's OK
}
}
$this->values[] = $value;
$this->buffer[] = self::PARAM_TYPE_MAP[$type] ?? '%s';
}
}
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Doctrine\DBAL\Driver\AbstractMySQLDriver;
class Driver extends AbstractMySQLDriver {
public function connect(array $params): Connection {
return new Connection();
}
}
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB\Exceptions;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Doctrine\DBAL\Driver\AbstractException;
class ConnectionException extends AbstractException {
}
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB\Exceptions;
if (!defined('ABSPATH')) exit;
use Exception;
class MissingParameterException extends Exception {
}
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB\Exceptions;
if (!defined('ABSPATH')) exit;
use Exception;
class NotSupportedException extends Exception {
}
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB\Exceptions;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Doctrine\DBAL\Driver\AbstractException;
class QueryException extends AbstractException {
}
@@ -0,0 +1 @@
<?php
@@ -0,0 +1,75 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Doctrine\DBAL\Driver\Result as ResultInterface;
/**
* WPDB fetches all results from the underlying database driver,
* so we need to implement the result methods on in-memory data.
*/
class Result implements ResultInterface {
/** @var array[] */
private array $result = [];
private int $rowCount;
private int $cursor = 0;
public function __construct(
array $result,
int $rowCount
) {
foreach ($result as $row) {
$this->result[] = (array)$row;
}
$this->rowCount = $rowCount;
}
public function fetchNumeric() {
$row = $this->result[$this->cursor++] ?? null;
return $row === null ? false : array_values($row);
}
public function fetchAssociative() {
return $this->result[$this->cursor++] ?? false;
}
public function fetchOne() {
$row = $this->result[$this->cursor++] ?? null;
return $row === null ? false : reset($row);
}
public function fetchAllNumeric(): array {
$result = [];
foreach ($this->result as $row) {
$result[] = array_values($row);
}
return $result;
}
public function fetchAllAssociative(): array {
return $this->result;
}
public function fetchFirstColumn(): array {
$result = [];
foreach ($this->result as $row) {
$result[] = reset($row);
}
return $result;
}
public function rowCount(): int {
return $this->rowCount;
}
public function columnCount(): int {
return count($this->result[0] ?? []);
}
public function free(): void {
$this->cursor = 0;
}
}
@@ -0,0 +1,73 @@
<?php declare(strict_types = 1);
namespace MailPoet\Doctrine\WPDB;
if (!defined('ABSPATH')) exit;
use MailPoet\Doctrine\WPDB\Exceptions\NotSupportedException;
use MailPoetVendor\Doctrine\DBAL\Driver\Result;
use MailPoetVendor\Doctrine\DBAL\Driver\Statement as StatementInterface;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use MailPoetVendor\Doctrine\DBAL\SQL\Parser;
class Statement implements StatementInterface {
private Connection $connection;
private Parser $parser;
private string $sql;
private array $params = [];
public function __construct(
Connection $connection,
string $sql
) {
$this->connection = $connection;
$this->parser = new Parser(false);
$this->sql = $sql;
}
/**
* @param string|int $param
* @param mixed $value
* @param int $type
* @return true
*/
public function bindValue($param, $value, $type = ParameterType::STRING) {
$this->params[$param] = [$param, $value, $type];
return true;
}
/**
* @param string|int $param
* @param mixed $variable
* @param int $type
* @param int|null $length
* @return true
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) {
throw new NotSupportedException(
'Statement::bindParam() is deprecated in Doctrine and not implemented in WPDB driver. Use Statement::bindValue() instead.'
);
}
public function execute($params = null): Result {
if ($params !== null) {
throw new NotSupportedException(
'Statement::execute() with parameters is deprecated in Doctrine and not implemented in WPDB driver. Use Statement::bindValue() instead.'
);
}
// Convert '?' parameters to WPDB format (sprintf-like: '%s', '%d', ...),
// and add support for named parameters that are not supported by mysqli.
$visitor = new ConvertParameters($this->params);
$this->parser->parse($this->sql, $visitor);
$sql = $visitor->getSQL();
$values = $visitor->getValues();
global $wpdb;
$query = count($values) > 0
? $wpdb->prepare($sql, $values) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
: $sql;
return $this->connection->query($query);
}
}
@@ -0,0 +1 @@
<?php