PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Module / WpdbAdapter.php

WpdbAdapter.php in Packeta trunk, at src/Packetery/Module/WpdbAdapter.php

482 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class WpdbAdapter
4 *
5 * @package Packetery
6 */
7
8 declare( strict_types=1 );
9
10 namespace Packetery\Module;
11
12 use Packetery\Module\Exception\DeleteErrorException;
13 use Packetery\Module\Framework\WcAdapter;
14
15 /**
16 * Class WpdbAdapter
17 *
18 * @package Packetery
19 */
20 class WpdbAdapter {
21
22 /**
23 * Table name.
24 *
25 * @var string
26 */
27 public $packeteryCarrier;
28
29 /**
30 * Table name.
31 *
32 * @var string
33 */
34 public $packeteryOrder;
35
36 /**
37 * Table name.
38 *
39 * @var string
40 */
41 public $packeteryLog;
42
43 /**
44 * Table name.
45 *
46 * @var string
47 */
48 public $packeteryCustomsDeclaration;
49
50 /**
51 * Table name.
52 *
53 * @var string
54 */
55 public $packeteryCustomsDeclarationItem;
56
57 /**
58 * Table name.
59 *
60 * @var string
61 */
62 public $wcOrders;
63
64 /**
65 * Table name.
66 *
67 * @var string
68 */
69 public $posts;
70
71 /**
72 * Table name.
73 *
74 * @var string
75 */
76 public $options;
77
78 /**
79 * Table name.
80 *
81 * @var string
82 */
83 public $postmeta;
84
85 /**
86 * Wpdb.
87 *
88 * @var \wpdb
89 */
90 private $wpdb;
91
92 /**
93 * @var WcAdapter
94 */
95 private $wcAdapter;
96
97 /**
98 * Constructor.
99 *
100 * @param \wpdb $wpdb Wpdb.
101 */
102 public function __construct( \wpdb $wpdb, WcAdapter $wcAdapter ) {
103 $this->wpdb = $wpdb;
104 $this->wcAdapter = $wcAdapter;
105 }
106
107 /**
108 * @param string $query SQL query.
109 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
110 * correspond to an stdClass object, an associative array, or a numeric array,
111 * respectively. Default OBJECT.
112 *
113 * @return array<string, mixed>|object|null Database query result or null on failure.
114 */
115 public function get_row( string $query, string $output = OBJECT ) {
116 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
117 $result = $this->wpdb->get_row( $query, $output );
118 if ( $result === null ) {
119 $this->handleError();
120 }
121
122 return $result;
123 }
124
125 /**
126 * Prepares a SQL query for safe execution.
127 *
128 * @param string $query Query.
129 * @param mixed ...$args Arguments.
130 *
131 * @return string
132 */
133 public function prepare( string $query, ...$args ): string {
134 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
135 $result = $this->wpdb->prepare( $query, ...$args );
136 if ( $result === null ) {
137 $this->logError( 'Query to prepare is invalid. Likely due placeholder count mismatch.' );
138 }
139
140 return (string) $result;
141 }
142
143 /**
144 * Executes SQL query.
145 *
146 * @param string $query Query.
147 *
148 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
149 * affected/selected for all other queries. Boolean false on error.
150 */
151 public function query( string $query ) {
152 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
153 $result = $this->wpdb->query( $query );
154 if ( $result === false ) {
155 $this->handleError();
156 }
157
158 return $result;
159 }
160
161 /**
162 * Helper function for insert and replace.
163 *
164 * @param string $table Table name.
165 * @param array<string, mixed> $data Data to insert (in column => value pairs).
166 * @param string[]|null $format Optional. An array of formats to be mapped to each of the value in $data.
167 * @param string $type Optional. Type of operation. Possible values include 'INSERT' or 'REPLACE'.
168 *
169 * @return int|false The number of rows affected, or false on error.
170 */
171 public function insertReplaceHelper( string $table, array $data, ?array $format = null, string $type = 'INSERT' ) {
172 $result = $this->wpdb->_insert_replace_helper( $table, $data, $format, $type );
173 if ( $result === false ) {
174 $this->handleError();
175 }
176
177 return $result;
178 }
179
180 /**
181 * Deletes a row in the table.
182 *
183 * @param string $table Table name.
184 * @param array<string, int|string> $where A named array of WHERE clauses (in column => value pairs).
185 * @param string|null $whereFormat Optional. An array of formats to be mapped to each of the values in $where.
186 *
187 * @return int The number of rows deleted, throws DeleteErrorException on error.
188 * @throws DeleteErrorException
189 */
190 public function delete( string $table, array $where, ?string $whereFormat = null ): int {
191 $result = $this->wpdb->delete( $table, $where, $whereFormat );
192 if ( $result === false ) {
193 $this->handleError();
194
195 throw new DeleteErrorException( "Could not delete from table `{$table}`." );
196 }
197
198 return $result;
199 }
200
201 /**
202 * Inserts a row into the table.
203 *
204 * @param string $table Table name.
205 * @param array<string, mixed> $data Data to insert (in column => value pairs).
206 *
207 * @return int|false The number of rows inserted, or false on error.
208 */
209 public function insert( string $table, array $data ) {
210 $result = $this->wpdb->insert( $table, $data );
211 if ( $result === false ) {
212 $this->handleError();
213 }
214
215 return $result;
216 }
217
218 /**
219 * Updates a row in the table.
220 *
221 * @param string $table Table name.
222 * @param array<string, int|float|string|null|bool> $data Data to update (in column => value pairs).
223 * @param array<string, int|string> $where A named array of WHERE clauses (in column => value pairs).
224 *
225 * @return int|false The number of rows updated, or false on error.
226 */
227 public function update( string $table, array $data, array $where ) {
228 $result = $this->wpdb->update( $table, $data, $where );
229 if ( $result === false ) {
230 $this->handleError();
231 }
232
233 return $result;
234 }
235
236 /**
237 * Gets charset collate.
238 *
239 * @return string
240 */
241 public function get_charset_collate(): string {
242 return $this->wpdb->get_charset_collate();
243 }
244
245 /**
246 * Retrieves an entire SQL result set from the database (i.e., many rows).
247 *
248 * @param string $query SQL query.
249 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
250 *
251 * @return array|object[]|null Database query results.
252 */
253 public function get_results( string $query, string $output = OBJECT ): ?array {
254 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
255 $result = $this->wpdb->get_results( $query, $output );
256 $this->handleError();
257
258 return $result;
259 }
260
261 /**
262 * Retrieves one variable from the database.
263 *
264 * @param string $query SQL query. Defaults to null, use the result from the previous query.
265 *
266 * @return string|null Database query result (as string), or null on failure.
267 */
268 public function get_var( string $query ): ?string {
269 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
270 $result = $this->wpdb->get_var( $query );
271 if ( $result === null ) {
272 $this->handleError();
273 }
274
275 return $result;
276 }
277
278 /**
279 * Tells if packetery table is queried.
280 *
281 * @param string $query Query.
282 *
283 * @return bool
284 */
285 private function isPacketeryTableQueried( string $query ): bool {
286 return preg_match( '~\s*(FROM|JOIN|INTO|UPDATE|TABLE)\s*`?' . preg_quote( $this->getPacketeryPrefix(), '~' ) . '~i', $query ) === 1;
287 }
288
289 /**
290 * Gets packetery prefix.
291 *
292 * @return string
293 */
294 public function getPacketeryPrefix(): string {
295 return sprintf( '%spacketery_', $this->wpdb->prefix );
296 }
297
298 /**
299 * Logs wpdb error.
300 *
301 * @param string $errorMessage Error message.
302 *
303 * @return void
304 */
305 private function logError( string $errorMessage ): void {
306 $wcLogger = $this->wcAdapter->getLogger();
307 $wcLogger->error( sprintf( 'wpdb: %s', $errorMessage ), [ 'source' => 'packeta' ] );
308 }
309
310 /**
311 * Handles wpdb error.
312 *
313 * @return void
314 */
315 private function handleError(): void {
316 if ( $this->getLastWpdbError() !== '' && $this->isPacketeryTableQueried( (string) $this->wpdb->last_query ) ) {
317 $this->logError( $this->getLastWpdbError() );
318 }
319 }
320
321 /**
322 * Gets last wpdb error.
323 *
324 * @return string
325 */
326 public function getLastWpdbError(): string {
327 return $this->wpdb->last_error;
328 }
329
330 /**
331 * Gets wpdb queries.
332 *
333 * @return \Generator
334 */
335 public function getWpdbQueries(): \Generator {
336 if ( $this->wpdb->queries !== null ) {
337 foreach ( $this->wpdb->queries as $queryInfo ) {
338 yield $queryInfo;
339 }
340 }
341 }
342
343 /**
344 * This method outputs a one dimensional array. If more than one column is returned by the query,
345 * only the specified column will be returned, but the entire result is cached for later use.
346 *
347 * @param string $query The query you wish to execute. Setting this parameter to null will return the specified column from the cached results of the previous query.
348 * @param int $columnOffset The desired column (0 being the first). Defaults to 0.
349 *
350 * @return array<int|float|string|null|bool> Returns an empty array if no result is found.
351 */
352 public function get_col( string $query, int $columnOffset = 0 ): array {
353 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
354 $result = $this->wpdb->get_col( $query, $columnOffset );
355 if ( $result === [] ) {
356 $this->handleError();
357 }
358
359 return $result;
360 }
361
362 /**
363 * Quote array of strings.
364 *
365 * @param string[] $input Input.
366 *
367 * @return string[]
368 */
369 private function quoteArrayOfStrings( array $input ): array {
370 return array_map(
371 function ( string $item ) {
372 return $this->prepare( '%s', $item );
373 },
374 $input
375 );
376 }
377
378 /**
379 * Prepare IN clause from array of strings.
380 *
381 * @param string[] $input Input array.
382 *
383 * @return string
384 */
385 public function prepareInClause( array $input ): string {
386 return implode( ',', $this->quoteArrayOfStrings( $input ) );
387 }
388
389 /**
390 * Wrapper for dbDelta function, logs result.
391 *
392 * @param string $createTableQuery Create table query.
393 * @param string $tableName Table name.
394 *
395 * @return bool
396 */
397 public function dbDelta( string $createTableQuery, string $tableName ): bool {
398 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
399 $result1 = dbDelta( $createTableQuery );
400 $result2 = dbDelta( $createTableQuery );
401
402 $wcLogger = $this->wcAdapter->getLogger();
403 foreach ( $result1 as $tableOrColumn => $message ) {
404 $wcLogger->info( sprintf( 'dbDelta: %s => %s', $tableOrColumn, $message ), [ 'source' => 'packeta' ] );
405 }
406
407 $parsedResult1 = $this->parseDbdeltaOutput( $result1 );
408 $parsedResult2 = $this->parseDbdeltaOutput( $result2 );
409 // If the first command tries to create the table and so does the second, it means it failed.
410 if (
411 in_array( $tableName, $parsedResult1['created_tables'], true ) &&
412 in_array( $tableName, $parsedResult2['created_tables'], true )
413 ) {
414 return false;
415 }
416 // If the first command tries to add column and so does the second, it means it failed.
417 if ( $parsedResult1['added_columns'] !== [] && $parsedResult2['added_columns'] !== [] ) {
418 return false;
419 }
420
421 // Otherwise, we assume everything is fine, column changes errors are not safe to catch this way.
422 return true;
423 }
424
425 /**
426 * Parses the output given by dbDelta and returns information about it. Taken from DatabaseUtil 7.5.1.
427 *
428 * @param array<int|string, string> $dbdeltaOutput The output from the execution of dbDelta.
429 *
430 * An array containing a 'created_tables' and 'added_columns' key whose value is an array with the names of the tables or columns that have been (or would have been) created.
431 * @return array{created_tables: array<int<0, max>, (int|string)>, added_columns: array<int<0, max>, (int|string)>}
432 */
433 private function parseDbdeltaOutput( array $dbdeltaOutput ): array {
434 $createdTables = [];
435 $addedColumns = [];
436
437 foreach ( $dbdeltaOutput as $tableOrColumn => $result ) {
438 if ( "Created table $tableOrColumn" === $result ) {
439 $createdTables[] = $tableOrColumn;
440
441 continue;
442 }
443 if ( "Added column $tableOrColumn" === $result ) {
444 $addedColumns[] = $tableOrColumn;
445 }
446 }
447
448 return [
449 'created_tables' => $createdTables,
450 'added_columns' => $addedColumns,
451 ];
452 }
453
454 /**
455 * Gets last insert ID.
456 *
457 * @return string|null
458 */
459 public function getLastInsertId(): ?string {
460 if ( $this->wpdb->insert_id === 0 ) {
461 return null;
462 }
463
464 return (string) $this->wpdb->insert_id;
465 }
466
467 /**
468 * Wpdb esc_like method proxy.
469 *
470 * @param string $text Text to escape.
471 *
472 * @return string
473 */
474 public function escLike( string $text ): string {
475 return $this->wpdb->esc_like( $text );
476 }
477
478 public function dbServerInfo(): string {
479 return $this->wpdb->db_server_info();
480 }
481 }
482