PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta
16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / vendor / wp-php-toolkit / reprint-exporter / src / class-wpdb-driver-pdo.php
jetpack / vendor / wp-php-toolkit / reprint-exporter / src Last commit date
class-file-tree-producer.php 6 days ago class-hmac-client.php 6 days ago class-hmac-server.php 6 days ago class-http-server.php 6 days ago class-mysql-dump-producer.php 6 days ago class-pdo-polyfill.php 6 days ago class-sqlite-driver-pdo.php 6 days ago class-staged-artifacts.php 6 days ago class-staged-endpoints.php 6 days ago class-staged-push-stream-protocol.php 6 days ago class-wpdb-driver-pdo.php 6 days ago export.php 6 days ago utils.php 6 days ago
class-wpdb-driver-pdo.php
272 lines
1 <?php
2 /**
3 * wpdb-backed PDO adapter for MySQL exports on PDO-less hosts.
4 *
5 * MySQLDumpProducer expects a PDO connection — prepare(), query(), quote(),
6 * and the statement methods fetch(), fetchAll(), fetchColumn(), execute().
7 * On hosts without ext-pdo / ext-pdo_mysql, this adapter wraps WordPress's
8 * global $wpdb so the dump producer can run unchanged.
9 *
10 * Surface area mirrors SqliteDriverPDO 1:1 — only the methods MySQLDumpProducer
11 * and the export endpoints actually call. Behavior parity with real PDO is the
12 * bar; behavioral divergence (wpdb's HTML error rendering, sticky last_error,
13 * get_results null ambiguity) is normalized inside the adapter.
14 *
15 * Charset: wpdb uses the site's DB_CHARSET. The dump producer wraps non-numeric
16 * columns in CAST(... AS BINARY) so the connection charset doesn't influence
17 * emitted bytes — same guarantee as the real-PDO path.
18 */
19
20 /**
21 * Wraps a WordPress wpdb instance to look like a PDO connection.
22 */
23 class WpdbDriverPDO
24 {
25 /** @var object */
26 private $wpdb;
27
28 /**
29 * @param object $wpdb The global WordPress $wpdb. Type hint omitted because
30 * wpdb subclasses (HyperDB, LudicrousDB, SQLite drop-in) are not
31 * guaranteed to extend the canonical class in all environments,
32 * and the test double is not a wpdb subclass.
33 */
34 public function __construct($wpdb)
35 {
36 $this->wpdb = $wpdb;
37
38 // Prevent wpdb from echoing HTML error blocks into the response stream.
39 // The export endpoints emit gzip multipart, so even one HTML chunk
40 // would corrupt the output stream irrecoverably. No restore on
41 // shutdown: export endpoints terminate the request after streaming.
42 $wpdb->suppress_errors(true);
43 $wpdb->hide_errors();
44 }
45
46 public function prepare(string $sql): WpdbDriverPDOStatement
47 {
48 return new WpdbDriverPDOStatement($this->wpdb, $sql);
49 }
50
51 public function query(string $sql): WpdbDriverPDOStatement
52 {
53 $stmt = new WpdbDriverPDOStatement($this->wpdb, $sql);
54 $stmt->execute();
55 return $stmt;
56 }
57
58 /**
59 * Quotes a string for safe inclusion in a query.
60 *
61 * Calls $wpdb->_real_escape() — a public method on wpdb (and its
62 * subclasses); the leading underscore is naming convention only, not
63 * visibility. Calling it directly keeps the adapter free of any global
64 * function dependency and HyperDB / LudicrousDB / SQLite-drop-in safe.
65 */
66 public function quote(string $value, int $type = PDO::PARAM_STR): string
67 {
68 return "'" . $this->wpdb->_real_escape($value) . "'";
69 }
70 }
71
72 /**
73 * PDOStatement-shaped wrapper around a wpdb query result set.
74 *
75 * Substitutes positional (?) and named (:name) placeholders manually rather
76 * than calling $wpdb->prepare() — wpdb's prepare uses %s/%d and adds quoting
77 * itself, which would conflict with how MySQLDumpProducer builds queries.
78 */
79 class WpdbDriverPDOStatement
80 {
81 /** @var object */
82 private $wpdb;
83
84 /** @var string */
85 private $sql;
86
87 /** @var array Stored result rows after execution. */
88 private $rows = [];
89
90 /** @var int Current position for fetch(). */
91 private $position = 0;
92
93 /** @var array Parameters bound via bindValue(). */
94 private $bound_params = [];
95
96 /**
97 * @param object $wpdb
98 * @param string $sql
99 */
100 public function __construct($wpdb, string $sql)
101 {
102 $this->wpdb = $wpdb;
103 $this->sql = $sql;
104 }
105
106 /**
107 * Executes the prepared statement.
108 *
109 * Substitutes parameters into $this->sql, clears any sticky
110 * $wpdb->last_error (which can survive from queries that ran before the
111 * exporter took over the request), runs $wpdb->get_results($sql,
112 * ARRAY_A), and disambiguates the null return: null + non-empty
113 * last_error -> \PDOException; null + empty last_error -> empty result
114 * set.
115 *
116 * @param array|null $params Positional or named parameters.
117 */
118 public function execute($params = null): bool
119 {
120 $sql = $this->substitute_placeholders($this->sql, $params ?? $this->bound_params);
121
122 // Clear sticky last_error so the post-dispatch check below can't
123 // throw a phantom PDOException from a prior query.
124 $this->clear_last_error();
125
126 $rows = $this->wpdb->get_results($sql, 'ARRAY_A');
127 $last_error = $this->get_last_error();
128
129 if ($last_error !== '') {
130 throw new \PDOException($last_error);
131 }
132
133 $this->rows = is_array($rows) ? $rows : [];
134 $this->position = 0;
135 return true;
136 }
137
138 /**
139 * $mode is accepted for PDO compatibility but only FETCH_ASSOC is honored.
140 * MySQLDumpProducer never asks for any other mode.
141 */
142 public function fetch(int $mode = PDO::FETCH_ASSOC)
143 {
144 if ($this->position >= count($this->rows)) {
145 return false;
146 }
147 return $this->rows[$this->position++];
148 }
149
150 /**
151 * Honors FETCH_ASSOC (default) and FETCH_COLUMN. Other PDO modes are
152 * unsupported — MySQLDumpProducer never asks for any other mode.
153 */
154 public function fetchAll(int $mode = PDO::FETCH_ASSOC): array
155 {
156 $remaining = array_slice($this->rows, $this->position);
157 $this->position = count($this->rows);
158
159 if ($mode === PDO::FETCH_COLUMN) {
160 return array_map(static function ($row) {
161 return reset($row);
162 }, $remaining);
163 }
164
165 return $remaining;
166 }
167
168 public function fetchColumn(int $column_number = 0)
169 {
170 $row = $this->fetch();
171 if ($row === false) {
172 return false;
173 }
174 $values = array_values($row);
175 return $values[$column_number] ?? false;
176 }
177
178 public function bindValue($parameter, $value, int $type = PDO::PARAM_STR): bool
179 {
180 $this->bound_params[$parameter] = $value;
181 return true;
182 }
183
184 /**
185 * Substitutes ? and :name placeholders with quoted parameter values.
186 *
187 * Walks $sql byte-by-byte tracking single/double-quoted string literals so
188 * a literal '?' or ':name' inside a string is not treated as a placeholder.
189 * Inside a literal, a backslash escapes the next byte (MySQL extension)
190 * so `\'` does not close the string. Positional substitution runs right
191 * to left so earlier offsets stay valid. Named substitution uses a regex
192 * with a word-boundary tail (/:name(?![A-Za-z0-9_])/) so :foo does not
193 * corrupt :foobar.
194 */
195 private function clear_last_error(): void
196 {
197 $this->wpdb->last_error = '';
198 }
199
200 private function get_last_error(): string
201 {
202 return (string) ($this->wpdb->last_error ?? '');
203 }
204
205 private function substitute_placeholders(string $sql, $params): string
206 {
207 if ($params === null || count($params) === 0) {
208 return $sql;
209 }
210
211 $positions = [];
212 $len = strlen($sql);
213 $in_single = false;
214 $in_double = false;
215 $escape_next = false;
216 for ($i = 0; $i < $len; $i++) {
217 if ($escape_next) {
218 $escape_next = false;
219 continue;
220 }
221 $ch = $sql[$i];
222 if (($in_single || $in_double) && $ch === '\\') {
223 $escape_next = true;
224 continue;
225 }
226 if ($ch === "'" && !$in_double) {
227 $in_single = !$in_single;
228 } elseif ($ch === '"' && !$in_single) {
229 $in_double = !$in_double;
230 } elseif ($ch === '?' && !$in_single && !$in_double) {
231 $positions[] = $i;
232 }
233 }
234
235 for ($i = count($positions) - 1; $i >= 0; $i--) {
236 if (!array_key_exists($i, $params)) {
237 continue;
238 }
239 $quoted = $this->quote_value($params[$i]);
240 $sql = substr_replace($sql, $quoted, $positions[$i], 1);
241 }
242
243 foreach ($params as $key => $value) {
244 if (!is_string($key)) {
245 continue;
246 }
247 $name = ltrim($key, ':');
248 if ($name === '') {
249 continue;
250 }
251 $pattern = '/:' . preg_quote($name, '/') . '(?![A-Za-z0-9_])/';
252 $sql = preg_replace_callback(
253 $pattern,
254 function () use ($value) {
255 return $this->quote_value($value);
256 },
257 $sql
258 );
259 }
260
261 return $sql;
262 }
263
264 private function quote_value($value): string
265 {
266 if ($value === null) {
267 return 'NULL';
268 }
269 return "'" . $this->wpdb->_real_escape((string) $value) . "'";
270 }
271 }
272