PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 All 503 releases
jetpack / vendor / wp-php-toolkit / reprint-server / src / class-sqlite-driver-pdo.php

class-sqlite-driver-pdo.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.1, at vendor/wp-php-toolkit/reprint-server/src/class-sqlite-driver-pdo.php

286 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PDO-compatible adapter for the SQLite Database Integration driver.
4 *
5 * MySQLDumpProducer expects a PDO connection — prepare(), query(), and the
6 * statement methods fetch(), fetchAll(), fetchColumn(), execute().
7 * On SQLite sites, the plugin's db.php drop-in loads a driver which translates
8 * MySQL queries to SQLite. This adapter supplies the PDO operations which that
9 * driver does not implement so the dump producer can use either supported
10 * plugin version without knowing it is talking to SQLite.
11 *
12 * Every MySQL query goes through the active driver's translator, which converts
13 * it to SQLite on the fly. The result is transparent: the dump producer sends
14 * MySQL queries, gets rows back, and produces valid MySQL SQL output.
15 */
16
17 namespace WordPress\Reprint\Server;
18
19 use PDO;
20 use PDOStatement;
21
22 /**
23 * Wraps a supported SQLite Database Integration driver as a PDO connection.
24 *
25 * Only the methods that MySQLDumpProducer and the export endpoints actually
26 * use are implemented. Anything else will trigger a clear PHP error rather
27 * than silently misbehaving.
28 */
29 class SqliteDriverPDO
30 {
31 /** @var object The plugin's MySQL-on-SQLite driver. */
32 private $driver;
33
34 /** @var PDO The raw SQLite PDO for quote() delegation. */
35 private $raw_pdo;
36
37 /**
38 * @param object $driver WP_SQLite_Driver or WP_MySQL_On_SQLite.
39 * @param PDO $raw_pdo The underlying SQLite connection.
40 */
41 public function __construct($driver, PDO $raw_pdo)
42 {
43 $this->driver = $driver;
44 $this->raw_pdo = $raw_pdo;
45 }
46
47 /**
48 * Prepares a statement for execution.
49 *
50 * Returns a SqliteDriverPDOStatement that will substitute parameters
51 * and execute through the driver when execute() is called.
52 */
53 public function prepare(string $sql): SqliteDriverPDOStatement
54 {
55 return new SqliteDriverPDOStatement($this->driver, $this->raw_pdo, $sql);
56 }
57
58 /**
59 * Executes a query immediately and returns the result set.
60 */
61 public function query(string $sql): SqliteDriverPDOStatement
62 {
63 $stmt = new SqliteDriverPDOStatement($this->driver, $this->raw_pdo, $sql);
64 $stmt->execute();
65 return $stmt;
66 }
67
68 /**
69 * Quotes a string for safe inclusion in a query.
70 * Delegates to the underlying raw SQLite PDO.
71 */
72 public function quote(string $value, int $type = PDO::PARAM_STR): string
73 {
74 return $this->raw_pdo->quote($value, $type);
75 }
76 }
77
78 /**
79 * PDOStatement-compatible wrapper for MySQL-on-SQLite query results.
80 *
81 * Collects all result rows eagerly after execution and serves them
82 * through fetch/fetchAll/fetchColumn.
83 */
84 class SqliteDriverPDOStatement
85 {
86 /** @var object The plugin's MySQL-on-SQLite driver. */
87 private $driver;
88
89 /** @var PDO The raw SQLite PDO for quote() delegation. */
90 private $raw_pdo;
91
92 /** @var string */
93 private $sql;
94
95 /** @var array Stored result rows after execution. */
96 private $rows = [];
97
98 /** @var int Current position for fetch(). */
99 private $position = 0;
100
101 /** @var array|null Parameters bound via bindValue(). */
102 private $bound_params = null;
103
104 /**
105 * @param object $driver WP_SQLite_Driver or WP_MySQL_On_SQLite.
106 * @param PDO $raw_pdo The underlying SQLite connection.
107 * @param string $sql MySQL query to execute.
108 */
109 public function __construct($driver, PDO $raw_pdo, string $sql)
110 {
111 $this->driver = $driver;
112 $this->raw_pdo = $raw_pdo;
113 $this->sql = $sql;
114 }
115
116 /**
117 * Executes the prepared statement.
118 *
119 * Substitutes bound parameters into the query, sends it through
120 * the plugin's MySQL-on-SQLite driver, and stores the result rows.
121 *
122 * @param array|null $params Positional or named parameters.
123 * @return bool True on success.
124 */
125 public function execute($params = null): bool
126 {
127 // Merge in any parameters set via bindValue().
128 if ($params === null && $this->bound_params !== null) {
129 $params = $this->bound_params;
130 }
131
132 $sql = $this->sql;
133
134 if ($params !== null && count($params) > 0) {
135 // Find ? placeholder positions outside of string literals.
136 $positions = [];
137 $len = strlen($sql);
138 $in_single = false;
139 $in_double = false;
140 for ($i = 0; $i < $len; $i++) {
141 $ch = $sql[$i];
142 if ($ch === "'" && !$in_double) {
143 $in_single = !$in_single;
144 } elseif ($ch === '"' && !$in_single) {
145 $in_double = !$in_double;
146 } elseif ($ch === '?' && !$in_single && !$in_double) {
147 $positions[] = $i;
148 }
149 }
150
151 // Replace positional placeholders from right to left so
152 // earlier offsets stay valid.
153 for ($i = count($positions) - 1; $i >= 0; $i--) {
154 if (!array_key_exists($i, $params)) {
155 continue;
156 }
157 $quoted = $this->raw_pdo->quote($params[$i]);
158 $sql = substr_replace($sql, $quoted, $positions[$i], 1);
159 }
160
161 // Named parameters (:name style).
162 foreach ($params as $key => $value) {
163 if (!is_string($key)) {
164 continue;
165 }
166 $sql = str_replace($key, $this->raw_pdo->quote($value), $sql);
167 }
168 }
169
170 $result = $this->driver->query($sql);
171 $supply_table_types = false;
172 if ($result === false && strcasecmp(trim($sql), 'SHOW FULL TABLES') === 0) {
173 // The version 2 driver cannot parse SHOW FULL TABLES. Its public
174 // SHOW TABLE STATUS path already removes SQLite system tables.
175 $result = $this->driver->query('SHOW TABLE STATUS;');
176 $supply_table_types = true;
177 }
178 if ($result instanceof PDOStatement) {
179 $this->rows = $result->fetchAll(PDO::FETCH_ASSOC);
180 } else {
181 $result = $this->driver->get_query_results();
182 $this->rows = is_array($result) ? $result : [];
183 }
184
185 if (
186 count($this->rows) === 0 &&
187 preg_match(
188 '/\ASHOW (?:INDEX|FULL COLUMNS) FROM `([A-Za-z0-9_$]+)`\z/i',
189 trim($sql),
190 $matches
191 )
192 ) {
193 // The version 2 parser treats backticks as part of the table name
194 // in these SHOW forms. Retry only identifiers which are safe bare.
195 $legacy_sql = str_replace("`{$matches[1]}`", $matches[1], trim($sql));
196 $result = $this->driver->query($legacy_sql);
197 if ($result instanceof PDOStatement) {
198 $this->rows = $result->fetchAll(PDO::FETCH_ASSOC);
199 } else {
200 $result = $this->driver->get_query_results();
201 $this->rows = is_array($result) ? $result : [];
202 }
203 }
204
205 // The version 2 driver returns arrays of objects. Convert those rows
206 // to the same associative arrays returned by the version 3 driver.
207 foreach ($this->rows as $i => $row) {
208 if (is_object($row)) {
209 $this->rows[$i] = (array) $row;
210 }
211 if ($supply_table_types) {
212 $this->rows[$i] = [
213 'Name' => $this->rows[$i]['Name'],
214 'Table_type' => 'BASE TABLE',
215 ];
216 }
217 }
218
219 $this->position = 0;
220 return true;
221 }
222
223 /**
224 * Fetches the next row from the result set.
225 *
226 * @param int $mode Fetch mode (ignored — always returns associative array).
227 * @return array|false Associative array or false when exhausted.
228 */
229 public function fetch($mode = PDO::FETCH_ASSOC)
230 {
231 if ($this->position >= count($this->rows)) {
232 return false;
233 }
234 return $this->rows[$this->position++];
235 }
236
237 /**
238 * Returns all remaining rows from the result set.
239 *
240 * @param int $mode Fetch mode. Supports FETCH_ASSOC (default) and FETCH_COLUMN.
241 * @return array
242 */
243 public function fetchAll($mode = PDO::FETCH_ASSOC)
244 {
245 $remaining = array_slice($this->rows, $this->position);
246 $this->position = count($this->rows);
247
248 if ($mode === PDO::FETCH_COLUMN) {
249 return array_map(function ($row) {
250 return reset($row);
251 }, $remaining);
252 }
253
254 return $remaining;
255 }
256
257 /**
258 * Returns a single column from the next row.
259 *
260 * @param int $column_number 0-indexed column number.
261 * @return mixed|false The column value, or false if no more rows.
262 */
263 public function fetchColumn(int $column_number = 0)
264 {
265 $row = $this->fetch();
266 if ($row === false) {
267 return false;
268 }
269 $values = array_values($row);
270 return $values[$column_number] ?? false;
271 }
272
273 /**
274 * Binds a value to a named or positional parameter.
275 */
276 public function bindValue($parameter, $value, int $type = PDO::PARAM_STR): bool
277 {
278 if ($this->bound_params === null) {
279 $this->bound_params = [];
280 }
281 $this->bound_params[$parameter] = $value;
282 return true;
283 }
284
285 }
286