1 <?php |
1 <?php |
2 /** |
|
3 * WordPress database access abstraction class |
|
4 * |
|
5 * Original code from {@link http://php.justinvincent.com Justin Vincent (justin@visunet.ie)} |
|
6 * |
|
7 * @package WordPress |
|
8 * @subpackage Database |
|
9 * @since 0.71 |
|
10 */ |
|
11 |
|
12 /** |
|
13 * @since 0.71 |
|
14 */ |
|
15 define( 'EZSQL_VERSION', 'WP1.25' ); |
|
16 |
|
17 /** |
|
18 * @since 0.71 |
|
19 */ |
|
20 define( 'OBJECT', 'OBJECT' ); |
|
21 // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase |
|
22 define( 'object', 'OBJECT' ); // Back compat. |
|
23 |
|
24 /** |
|
25 * @since 2.5.0 |
|
26 */ |
|
27 define( 'OBJECT_K', 'OBJECT_K' ); |
|
28 |
|
29 /** |
|
30 * @since 0.71 |
|
31 */ |
|
32 define( 'ARRAY_A', 'ARRAY_A' ); |
|
33 |
|
34 /** |
|
35 * @since 0.71 |
|
36 */ |
|
37 define( 'ARRAY_N', 'ARRAY_N' ); |
|
38 |
|
39 /** |
2 /** |
40 * WordPress database access abstraction class. |
3 * WordPress database access abstraction class. |
41 * |
4 * |
42 * This class is used to interact with a database without needing to use raw SQL statements. |
5 * This file is deprecated, use 'wp-includes/class-wpdb.php' instead. |
43 * By default, WordPress uses this class to instantiate the global $wpdb object, providing |
|
44 * access to the WordPress database. |
|
45 * |
6 * |
46 * It is possible to replace this class with your own by setting the $wpdb global variable |
7 * @deprecated 6.1.0 |
47 * in wp-content/db.php file to your class. The wpdb class will still be included, so you can |
8 * @package WordPress |
48 * extend it or simply use your own. |
|
49 * |
|
50 * @link https://developer.wordpress.org/reference/classes/wpdb/ |
|
51 * |
|
52 * @since 0.71 |
|
53 */ |
9 */ |
54 class wpdb { |
|
55 |
10 |
56 /** |
11 if ( function_exists( '_deprecated_file' ) ) { |
57 * Whether to show SQL/DB errors. |
12 // Note: WPINC may not be defined yet, so 'wp-includes' is used here. |
58 * |
13 _deprecated_file( basename( __FILE__ ), '6.1.0', 'wp-includes/class-wpdb.php' ); |
59 * Default is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY evaluate to true. |
14 } |
60 * |
|
61 * @since 0.71 |
|
62 * |
|
63 * @var bool |
|
64 */ |
|
65 public $show_errors = false; |
|
66 |
15 |
67 /** |
16 /** wpdb class */ |
68 * Whether to suppress errors during the DB bootstrapping. Default false. |
17 require_once __DIR__ . '/class-wpdb.php'; |
69 * |
|
70 * @since 2.5.0 |
|
71 * |
|
72 * @var bool |
|
73 */ |
|
74 public $suppress_errors = false; |
|
75 |
|
76 /** |
|
77 * The error encountered during the last query. |
|
78 * |
|
79 * @since 2.5.0 |
|
80 * |
|
81 * @var string |
|
82 */ |
|
83 public $last_error = ''; |
|
84 |
|
85 /** |
|
86 * The number of queries made. |
|
87 * |
|
88 * @since 1.2.0 |
|
89 * |
|
90 * @var int |
|
91 */ |
|
92 public $num_queries = 0; |
|
93 |
|
94 /** |
|
95 * Count of rows returned by the last query. |
|
96 * |
|
97 * @since 0.71 |
|
98 * |
|
99 * @var int |
|
100 */ |
|
101 public $num_rows = 0; |
|
102 |
|
103 /** |
|
104 * Count of rows affected by the last query. |
|
105 * |
|
106 * @since 0.71 |
|
107 * |
|
108 * @var int |
|
109 */ |
|
110 public $rows_affected = 0; |
|
111 |
|
112 /** |
|
113 * The ID generated for an AUTO_INCREMENT column by the last query (usually INSERT). |
|
114 * |
|
115 * @since 0.71 |
|
116 * |
|
117 * @var int |
|
118 */ |
|
119 public $insert_id = 0; |
|
120 |
|
121 /** |
|
122 * The last query made. |
|
123 * |
|
124 * @since 0.71 |
|
125 * |
|
126 * @var string |
|
127 */ |
|
128 public $last_query; |
|
129 |
|
130 /** |
|
131 * Results of the last query. |
|
132 * |
|
133 * @since 0.71 |
|
134 * |
|
135 * @var stdClass[]|null |
|
136 */ |
|
137 public $last_result; |
|
138 |
|
139 /** |
|
140 * Database query result. |
|
141 * |
|
142 * Possible values: |
|
143 * |
|
144 * - For successful SELECT, SHOW, DESCRIBE, or EXPLAIN queries: |
|
145 * - `mysqli_result` instance when the `mysqli` driver is in use |
|
146 * - `resource` when the older `mysql` driver is in use |
|
147 * - `true` for other query types that were successful |
|
148 * - `null` if a query is yet to be made or if the result has since been flushed |
|
149 * - `false` if the query returned an error |
|
150 * |
|
151 * @since 0.71 |
|
152 * |
|
153 * @var mysqli_result|resource|bool|null |
|
154 */ |
|
155 protected $result; |
|
156 |
|
157 /** |
|
158 * Cached column info, for sanity checking data before inserting. |
|
159 * |
|
160 * @since 4.2.0 |
|
161 * |
|
162 * @var array |
|
163 */ |
|
164 protected $col_meta = array(); |
|
165 |
|
166 /** |
|
167 * Calculated character sets keyed by table name. |
|
168 * |
|
169 * @since 4.2.0 |
|
170 * |
|
171 * @var string[] |
|
172 */ |
|
173 protected $table_charset = array(); |
|
174 |
|
175 /** |
|
176 * Whether text fields in the current query need to be sanity checked. |
|
177 * |
|
178 * @since 4.2.0 |
|
179 * |
|
180 * @var bool |
|
181 */ |
|
182 protected $check_current_query = true; |
|
183 |
|
184 /** |
|
185 * Flag to ensure we don't run into recursion problems when checking the collation. |
|
186 * |
|
187 * @since 4.2.0 |
|
188 * |
|
189 * @see wpdb::check_safe_collation() |
|
190 * @var bool |
|
191 */ |
|
192 private $checking_collation = false; |
|
193 |
|
194 /** |
|
195 * Saved info on the table column. |
|
196 * |
|
197 * @since 0.71 |
|
198 * |
|
199 * @var array |
|
200 */ |
|
201 protected $col_info; |
|
202 |
|
203 /** |
|
204 * Log of queries that were executed, for debugging purposes. |
|
205 * |
|
206 * @since 1.5.0 |
|
207 * @since 2.5.0 The third element in each query log was added to record the calling functions. |
|
208 * @since 5.1.0 The fourth element in each query log was added to record the start time. |
|
209 * @since 5.3.0 The fifth element in each query log was added to record custom data. |
|
210 * |
|
211 * @var array[] { |
|
212 * Array of arrays containing information about queries that were executed. |
|
213 * |
|
214 * @type array ...$0 { |
|
215 * Data for each query. |
|
216 * |
|
217 * @type string $0 The query's SQL. |
|
218 * @type float $1 Total time spent on the query, in seconds. |
|
219 * @type string $2 Comma-separated list of the calling functions. |
|
220 * @type float $3 Unix timestamp of the time at the start of the query. |
|
221 * @type array $4 Custom query data. |
|
222 * } |
|
223 * } |
|
224 */ |
|
225 public $queries; |
|
226 |
|
227 /** |
|
228 * The number of times to retry reconnecting before dying. Default 5. |
|
229 * |
|
230 * @since 3.9.0 |
|
231 * |
|
232 * @see wpdb::check_connection() |
|
233 * @var int |
|
234 */ |
|
235 protected $reconnect_retries = 5; |
|
236 |
|
237 /** |
|
238 * WordPress table prefix. |
|
239 * |
|
240 * You can set this to have multiple WordPress installations in a single database. |
|
241 * The second reason is for possible security precautions. |
|
242 * |
|
243 * @since 2.5.0 |
|
244 * |
|
245 * @var string |
|
246 */ |
|
247 public $prefix = ''; |
|
248 |
|
249 /** |
|
250 * WordPress base table prefix. |
|
251 * |
|
252 * @since 3.0.0 |
|
253 * |
|
254 * @var string |
|
255 */ |
|
256 public $base_prefix; |
|
257 |
|
258 /** |
|
259 * Whether the database queries are ready to start executing. |
|
260 * |
|
261 * @since 2.3.2 |
|
262 * |
|
263 * @var bool |
|
264 */ |
|
265 public $ready = false; |
|
266 |
|
267 /** |
|
268 * Blog ID. |
|
269 * |
|
270 * @since 3.0.0 |
|
271 * |
|
272 * @var int |
|
273 */ |
|
274 public $blogid = 0; |
|
275 |
|
276 /** |
|
277 * Site ID. |
|
278 * |
|
279 * @since 3.0.0 |
|
280 * |
|
281 * @var int |
|
282 */ |
|
283 public $siteid = 0; |
|
284 |
|
285 /** |
|
286 * List of WordPress per-site tables. |
|
287 * |
|
288 * @since 2.5.0 |
|
289 * |
|
290 * @see wpdb::tables() |
|
291 * @var string[] |
|
292 */ |
|
293 public $tables = array( |
|
294 'posts', |
|
295 'comments', |
|
296 'links', |
|
297 'options', |
|
298 'postmeta', |
|
299 'terms', |
|
300 'term_taxonomy', |
|
301 'term_relationships', |
|
302 'termmeta', |
|
303 'commentmeta', |
|
304 ); |
|
305 |
|
306 /** |
|
307 * List of deprecated WordPress tables. |
|
308 * |
|
309 * 'categories', 'post2cat', and 'link2cat' were deprecated in 2.3.0, db version 5539. |
|
310 * |
|
311 * @since 2.9.0 |
|
312 * |
|
313 * @see wpdb::tables() |
|
314 * @var string[] |
|
315 */ |
|
316 public $old_tables = array( 'categories', 'post2cat', 'link2cat' ); |
|
317 |
|
318 /** |
|
319 * List of WordPress global tables. |
|
320 * |
|
321 * @since 3.0.0 |
|
322 * |
|
323 * @see wpdb::tables() |
|
324 * @var string[] |
|
325 */ |
|
326 public $global_tables = array( 'users', 'usermeta' ); |
|
327 |
|
328 /** |
|
329 * List of Multisite global tables. |
|
330 * |
|
331 * @since 3.0.0 |
|
332 * |
|
333 * @see wpdb::tables() |
|
334 * @var string[] |
|
335 */ |
|
336 public $ms_global_tables = array( |
|
337 'blogs', |
|
338 'blogmeta', |
|
339 'signups', |
|
340 'site', |
|
341 'sitemeta', |
|
342 'sitecategories', |
|
343 'registration_log', |
|
344 ); |
|
345 |
|
346 /** |
|
347 * WordPress Comments table. |
|
348 * |
|
349 * @since 1.5.0 |
|
350 * |
|
351 * @var string |
|
352 */ |
|
353 public $comments; |
|
354 |
|
355 /** |
|
356 * WordPress Comment Metadata table. |
|
357 * |
|
358 * @since 2.9.0 |
|
359 * |
|
360 * @var string |
|
361 */ |
|
362 public $commentmeta; |
|
363 |
|
364 /** |
|
365 * WordPress Links table. |
|
366 * |
|
367 * @since 1.5.0 |
|
368 * |
|
369 * @var string |
|
370 */ |
|
371 public $links; |
|
372 |
|
373 /** |
|
374 * WordPress Options table. |
|
375 * |
|
376 * @since 1.5.0 |
|
377 * |
|
378 * @var string |
|
379 */ |
|
380 public $options; |
|
381 |
|
382 /** |
|
383 * WordPress Post Metadata table. |
|
384 * |
|
385 * @since 1.5.0 |
|
386 * |
|
387 * @var string |
|
388 */ |
|
389 public $postmeta; |
|
390 |
|
391 /** |
|
392 * WordPress Posts table. |
|
393 * |
|
394 * @since 1.5.0 |
|
395 * |
|
396 * @var string |
|
397 */ |
|
398 public $posts; |
|
399 |
|
400 /** |
|
401 * WordPress Terms table. |
|
402 * |
|
403 * @since 2.3.0 |
|
404 * |
|
405 * @var string |
|
406 */ |
|
407 public $terms; |
|
408 |
|
409 /** |
|
410 * WordPress Term Relationships table. |
|
411 * |
|
412 * @since 2.3.0 |
|
413 * |
|
414 * @var string |
|
415 */ |
|
416 public $term_relationships; |
|
417 |
|
418 /** |
|
419 * WordPress Term Taxonomy table. |
|
420 * |
|
421 * @since 2.3.0 |
|
422 * |
|
423 * @var string |
|
424 */ |
|
425 public $term_taxonomy; |
|
426 |
|
427 /** |
|
428 * WordPress Term Meta table. |
|
429 * |
|
430 * @since 4.4.0 |
|
431 * |
|
432 * @var string |
|
433 */ |
|
434 public $termmeta; |
|
435 |
|
436 // |
|
437 // Global and Multisite tables |
|
438 // |
|
439 |
|
440 /** |
|
441 * WordPress User Metadata table. |
|
442 * |
|
443 * @since 2.3.0 |
|
444 * |
|
445 * @var string |
|
446 */ |
|
447 public $usermeta; |
|
448 |
|
449 /** |
|
450 * WordPress Users table. |
|
451 * |
|
452 * @since 1.5.0 |
|
453 * |
|
454 * @var string |
|
455 */ |
|
456 public $users; |
|
457 |
|
458 /** |
|
459 * Multisite Blogs table. |
|
460 * |
|
461 * @since 3.0.0 |
|
462 * |
|
463 * @var string |
|
464 */ |
|
465 public $blogs; |
|
466 |
|
467 /** |
|
468 * Multisite Blog Metadata table. |
|
469 * |
|
470 * @since 5.1.0 |
|
471 * |
|
472 * @var string |
|
473 */ |
|
474 public $blogmeta; |
|
475 |
|
476 /** |
|
477 * Multisite Registration Log table. |
|
478 * |
|
479 * @since 3.0.0 |
|
480 * |
|
481 * @var string |
|
482 */ |
|
483 public $registration_log; |
|
484 |
|
485 /** |
|
486 * Multisite Signups table. |
|
487 * |
|
488 * @since 3.0.0 |
|
489 * |
|
490 * @var string |
|
491 */ |
|
492 public $signups; |
|
493 |
|
494 /** |
|
495 * Multisite Sites table. |
|
496 * |
|
497 * @since 3.0.0 |
|
498 * |
|
499 * @var string |
|
500 */ |
|
501 public $site; |
|
502 |
|
503 /** |
|
504 * Multisite Sitewide Terms table. |
|
505 * |
|
506 * @since 3.0.0 |
|
507 * |
|
508 * @var string |
|
509 */ |
|
510 public $sitecategories; |
|
511 |
|
512 /** |
|
513 * Multisite Site Metadata table. |
|
514 * |
|
515 * @since 3.0.0 |
|
516 * |
|
517 * @var string |
|
518 */ |
|
519 public $sitemeta; |
|
520 |
|
521 /** |
|
522 * Format specifiers for DB columns. |
|
523 * |
|
524 * Columns not listed here default to %s. Initialized during WP load. |
|
525 * Keys are column names, values are format types: 'ID' => '%d'. |
|
526 * |
|
527 * @since 2.8.0 |
|
528 * |
|
529 * @see wpdb::prepare() |
|
530 * @see wpdb::insert() |
|
531 * @see wpdb::update() |
|
532 * @see wpdb::delete() |
|
533 * @see wp_set_wpdb_vars() |
|
534 * @var array |
|
535 */ |
|
536 public $field_types = array(); |
|
537 |
|
538 /** |
|
539 * Database table columns charset. |
|
540 * |
|
541 * @since 2.2.0 |
|
542 * |
|
543 * @var string |
|
544 */ |
|
545 public $charset; |
|
546 |
|
547 /** |
|
548 * Database table columns collate. |
|
549 * |
|
550 * @since 2.2.0 |
|
551 * |
|
552 * @var string |
|
553 */ |
|
554 public $collate; |
|
555 |
|
556 /** |
|
557 * Database Username. |
|
558 * |
|
559 * @since 2.9.0 |
|
560 * |
|
561 * @var string |
|
562 */ |
|
563 protected $dbuser; |
|
564 |
|
565 /** |
|
566 * Database Password. |
|
567 * |
|
568 * @since 3.1.0 |
|
569 * |
|
570 * @var string |
|
571 */ |
|
572 protected $dbpassword; |
|
573 |
|
574 /** |
|
575 * Database Name. |
|
576 * |
|
577 * @since 3.1.0 |
|
578 * |
|
579 * @var string |
|
580 */ |
|
581 protected $dbname; |
|
582 |
|
583 /** |
|
584 * Database Host. |
|
585 * |
|
586 * @since 3.1.0 |
|
587 * |
|
588 * @var string |
|
589 */ |
|
590 protected $dbhost; |
|
591 |
|
592 /** |
|
593 * Database handle. |
|
594 * |
|
595 * Possible values: |
|
596 * |
|
597 * - `mysqli` instance when the `mysqli` driver is in use |
|
598 * - `resource` when the older `mysql` driver is in use |
|
599 * - `null` if the connection is yet to be made or has been closed |
|
600 * - `false` if the connection has failed |
|
601 * |
|
602 * @since 0.71 |
|
603 * |
|
604 * @var mysqli|resource|false|null |
|
605 */ |
|
606 protected $dbh; |
|
607 |
|
608 /** |
|
609 * A textual description of the last query/get_row/get_var call. |
|
610 * |
|
611 * @since 3.0.0 |
|
612 * |
|
613 * @var string |
|
614 */ |
|
615 public $func_call; |
|
616 |
|
617 /** |
|
618 * Whether MySQL is used as the database engine. |
|
619 * |
|
620 * Set in wpdb::db_connect() to true, by default. This is used when checking |
|
621 * against the required MySQL version for WordPress. Normally, a replacement |
|
622 * database drop-in (db.php) will skip these checks, but setting this to true |
|
623 * will force the checks to occur. |
|
624 * |
|
625 * @since 3.3.0 |
|
626 * |
|
627 * @var bool |
|
628 */ |
|
629 public $is_mysql = null; |
|
630 |
|
631 /** |
|
632 * A list of incompatible SQL modes. |
|
633 * |
|
634 * @since 3.9.0 |
|
635 * |
|
636 * @var string[] |
|
637 */ |
|
638 protected $incompatible_modes = array( |
|
639 'NO_ZERO_DATE', |
|
640 'ONLY_FULL_GROUP_BY', |
|
641 'STRICT_TRANS_TABLES', |
|
642 'STRICT_ALL_TABLES', |
|
643 'TRADITIONAL', |
|
644 'ANSI', |
|
645 ); |
|
646 |
|
647 /** |
|
648 * Whether to use mysqli over mysql. Default false. |
|
649 * |
|
650 * @since 3.9.0 |
|
651 * |
|
652 * @var bool |
|
653 */ |
|
654 private $use_mysqli = false; |
|
655 |
|
656 /** |
|
657 * Whether we've managed to successfully connect at some point. |
|
658 * |
|
659 * @since 3.9.0 |
|
660 * |
|
661 * @var bool |
|
662 */ |
|
663 private $has_connected = false; |
|
664 |
|
665 /** |
|
666 * Time when the last query was performed. |
|
667 * |
|
668 * Only set when `SAVEQUERIES` is defined and truthy. |
|
669 * |
|
670 * @since 1.5.0 |
|
671 * |
|
672 * @var float |
|
673 */ |
|
674 public $time_start = null; |
|
675 |
|
676 /** |
|
677 * The last SQL error that was encountered. |
|
678 * |
|
679 * @since 2.5.0 |
|
680 * |
|
681 * @var WP_Error|string |
|
682 */ |
|
683 public $error = null; |
|
684 |
|
685 /** |
|
686 * Connects to the database server and selects a database. |
|
687 * |
|
688 * Does the actual setting up |
|
689 * of the class properties and connection to the database. |
|
690 * |
|
691 * @since 2.0.8 |
|
692 * |
|
693 * @link https://core.trac.wordpress.org/ticket/3354 |
|
694 * |
|
695 * @param string $dbuser Database user. |
|
696 * @param string $dbpassword Database password. |
|
697 * @param string $dbname Database name. |
|
698 * @param string $dbhost Database host. |
|
699 */ |
|
700 public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) { |
|
701 if ( WP_DEBUG && WP_DEBUG_DISPLAY ) { |
|
702 $this->show_errors(); |
|
703 } |
|
704 |
|
705 // Use the `mysqli` extension if it exists unless `WP_USE_EXT_MYSQL` is defined as true. |
|
706 if ( function_exists( 'mysqli_connect' ) ) { |
|
707 $this->use_mysqli = true; |
|
708 |
|
709 if ( defined( 'WP_USE_EXT_MYSQL' ) ) { |
|
710 $this->use_mysqli = ! WP_USE_EXT_MYSQL; |
|
711 } |
|
712 } |
|
713 |
|
714 $this->dbuser = $dbuser; |
|
715 $this->dbpassword = $dbpassword; |
|
716 $this->dbname = $dbname; |
|
717 $this->dbhost = $dbhost; |
|
718 |
|
719 // wp-config.php creation will manually connect when ready. |
|
720 if ( defined( 'WP_SETUP_CONFIG' ) ) { |
|
721 return; |
|
722 } |
|
723 |
|
724 $this->db_connect(); |
|
725 } |
|
726 |
|
727 /** |
|
728 * Makes private properties readable for backward compatibility. |
|
729 * |
|
730 * @since 3.5.0 |
|
731 * |
|
732 * @param string $name The private member to get, and optionally process. |
|
733 * @return mixed The private member. |
|
734 */ |
|
735 public function __get( $name ) { |
|
736 if ( 'col_info' === $name ) { |
|
737 $this->load_col_info(); |
|
738 } |
|
739 |
|
740 return $this->$name; |
|
741 } |
|
742 |
|
743 /** |
|
744 * Makes private properties settable for backward compatibility. |
|
745 * |
|
746 * @since 3.5.0 |
|
747 * |
|
748 * @param string $name The private member to set. |
|
749 * @param mixed $value The value to set. |
|
750 */ |
|
751 public function __set( $name, $value ) { |
|
752 $protected_members = array( |
|
753 'col_meta', |
|
754 'table_charset', |
|
755 'check_current_query', |
|
756 ); |
|
757 if ( in_array( $name, $protected_members, true ) ) { |
|
758 return; |
|
759 } |
|
760 $this->$name = $value; |
|
761 } |
|
762 |
|
763 /** |
|
764 * Makes private properties check-able for backward compatibility. |
|
765 * |
|
766 * @since 3.5.0 |
|
767 * |
|
768 * @param string $name The private member to check. |
|
769 * @return bool If the member is set or not. |
|
770 */ |
|
771 public function __isset( $name ) { |
|
772 return isset( $this->$name ); |
|
773 } |
|
774 |
|
775 /** |
|
776 * Makes private properties un-settable for backward compatibility. |
|
777 * |
|
778 * @since 3.5.0 |
|
779 * |
|
780 * @param string $name The private member to unset |
|
781 */ |
|
782 public function __unset( $name ) { |
|
783 unset( $this->$name ); |
|
784 } |
|
785 |
|
786 /** |
|
787 * Sets $this->charset and $this->collate. |
|
788 * |
|
789 * @since 3.1.0 |
|
790 */ |
|
791 public function init_charset() { |
|
792 $charset = ''; |
|
793 $collate = ''; |
|
794 |
|
795 if ( function_exists( 'is_multisite' ) && is_multisite() ) { |
|
796 $charset = 'utf8'; |
|
797 if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) { |
|
798 $collate = DB_COLLATE; |
|
799 } else { |
|
800 $collate = 'utf8_general_ci'; |
|
801 } |
|
802 } elseif ( defined( 'DB_COLLATE' ) ) { |
|
803 $collate = DB_COLLATE; |
|
804 } |
|
805 |
|
806 if ( defined( 'DB_CHARSET' ) ) { |
|
807 $charset = DB_CHARSET; |
|
808 } |
|
809 |
|
810 $charset_collate = $this->determine_charset( $charset, $collate ); |
|
811 |
|
812 $this->charset = $charset_collate['charset']; |
|
813 $this->collate = $charset_collate['collate']; |
|
814 } |
|
815 |
|
816 /** |
|
817 * Determines the best charset and collation to use given a charset and collation. |
|
818 * |
|
819 * For example, when able, utf8mb4 should be used instead of utf8. |
|
820 * |
|
821 * @since 4.6.0 |
|
822 * |
|
823 * @param string $charset The character set to check. |
|
824 * @param string $collate The collation to check. |
|
825 * @return array { |
|
826 * The most appropriate character set and collation to use. |
|
827 * |
|
828 * @type string $charset Character set. |
|
829 * @type string $collate Collation. |
|
830 * } |
|
831 */ |
|
832 public function determine_charset( $charset, $collate ) { |
|
833 if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) { |
|
834 return compact( 'charset', 'collate' ); |
|
835 } |
|
836 |
|
837 if ( 'utf8' === $charset && $this->has_cap( 'utf8mb4' ) ) { |
|
838 $charset = 'utf8mb4'; |
|
839 } |
|
840 |
|
841 if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) { |
|
842 $charset = 'utf8'; |
|
843 $collate = str_replace( 'utf8mb4_', 'utf8_', $collate ); |
|
844 } |
|
845 |
|
846 if ( 'utf8mb4' === $charset ) { |
|
847 // _general_ is outdated, so we can upgrade it to _unicode_, instead. |
|
848 if ( ! $collate || 'utf8_general_ci' === $collate ) { |
|
849 $collate = 'utf8mb4_unicode_ci'; |
|
850 } else { |
|
851 $collate = str_replace( 'utf8_', 'utf8mb4_', $collate ); |
|
852 } |
|
853 } |
|
854 |
|
855 // _unicode_520_ is a better collation, we should use that when it's available. |
|
856 if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) { |
|
857 $collate = 'utf8mb4_unicode_520_ci'; |
|
858 } |
|
859 |
|
860 return compact( 'charset', 'collate' ); |
|
861 } |
|
862 |
|
863 /** |
|
864 * Sets the connection's character set. |
|
865 * |
|
866 * @since 3.1.0 |
|
867 * |
|
868 * @param mysqli|resource $dbh The connection returned by `mysqli_connect()` or `mysql_connect()`. |
|
869 * @param string $charset Optional. The character set. Default null. |
|
870 * @param string $collate Optional. The collation. Default null. |
|
871 */ |
|
872 public function set_charset( $dbh, $charset = null, $collate = null ) { |
|
873 if ( ! isset( $charset ) ) { |
|
874 $charset = $this->charset; |
|
875 } |
|
876 if ( ! isset( $collate ) ) { |
|
877 $collate = $this->collate; |
|
878 } |
|
879 if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) { |
|
880 $set_charset_succeeded = true; |
|
881 |
|
882 if ( $this->use_mysqli ) { |
|
883 if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) { |
|
884 $set_charset_succeeded = mysqli_set_charset( $dbh, $charset ); |
|
885 } |
|
886 |
|
887 if ( $set_charset_succeeded ) { |
|
888 $query = $this->prepare( 'SET NAMES %s', $charset ); |
|
889 if ( ! empty( $collate ) ) { |
|
890 $query .= $this->prepare( ' COLLATE %s', $collate ); |
|
891 } |
|
892 mysqli_query( $dbh, $query ); |
|
893 } |
|
894 } else { |
|
895 if ( function_exists( 'mysql_set_charset' ) && $this->has_cap( 'set_charset' ) ) { |
|
896 $set_charset_succeeded = mysql_set_charset( $charset, $dbh ); |
|
897 } |
|
898 if ( $set_charset_succeeded ) { |
|
899 $query = $this->prepare( 'SET NAMES %s', $charset ); |
|
900 if ( ! empty( $collate ) ) { |
|
901 $query .= $this->prepare( ' COLLATE %s', $collate ); |
|
902 } |
|
903 mysql_query( $query, $dbh ); |
|
904 } |
|
905 } |
|
906 } |
|
907 } |
|
908 |
|
909 /** |
|
910 * Changes the current SQL mode, and ensures its WordPress compatibility. |
|
911 * |
|
912 * If no modes are passed, it will ensure the current MySQL server modes are compatible. |
|
913 * |
|
914 * @since 3.9.0 |
|
915 * |
|
916 * @param array $modes Optional. A list of SQL modes to set. Default empty array. |
|
917 */ |
|
918 public function set_sql_mode( $modes = array() ) { |
|
919 if ( empty( $modes ) ) { |
|
920 if ( $this->use_mysqli ) { |
|
921 $res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' ); |
|
922 } else { |
|
923 $res = mysql_query( 'SELECT @@SESSION.sql_mode', $this->dbh ); |
|
924 } |
|
925 |
|
926 if ( empty( $res ) ) { |
|
927 return; |
|
928 } |
|
929 |
|
930 if ( $this->use_mysqli ) { |
|
931 $modes_array = mysqli_fetch_array( $res ); |
|
932 if ( empty( $modes_array[0] ) ) { |
|
933 return; |
|
934 } |
|
935 $modes_str = $modes_array[0]; |
|
936 } else { |
|
937 $modes_str = mysql_result( $res, 0 ); |
|
938 } |
|
939 |
|
940 if ( empty( $modes_str ) ) { |
|
941 return; |
|
942 } |
|
943 |
|
944 $modes = explode( ',', $modes_str ); |
|
945 } |
|
946 |
|
947 $modes = array_change_key_case( $modes, CASE_UPPER ); |
|
948 |
|
949 /** |
|
950 * Filters the list of incompatible SQL modes to exclude. |
|
951 * |
|
952 * @since 3.9.0 |
|
953 * |
|
954 * @param array $incompatible_modes An array of incompatible modes. |
|
955 */ |
|
956 $incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes ); |
|
957 |
|
958 foreach ( $modes as $i => $mode ) { |
|
959 if ( in_array( $mode, $incompatible_modes, true ) ) { |
|
960 unset( $modes[ $i ] ); |
|
961 } |
|
962 } |
|
963 |
|
964 $modes_str = implode( ',', $modes ); |
|
965 |
|
966 if ( $this->use_mysqli ) { |
|
967 mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" ); |
|
968 } else { |
|
969 mysql_query( "SET SESSION sql_mode='$modes_str'", $this->dbh ); |
|
970 } |
|
971 } |
|
972 |
|
973 /** |
|
974 * Sets the table prefix for the WordPress tables. |
|
975 * |
|
976 * @since 2.5.0 |
|
977 * |
|
978 * @param string $prefix Alphanumeric name for the new prefix. |
|
979 * @param bool $set_table_names Optional. Whether the table names, e.g. wpdb::$posts, |
|
980 * should be updated or not. Default true. |
|
981 * @return string|WP_Error Old prefix or WP_Error on error. |
|
982 */ |
|
983 public function set_prefix( $prefix, $set_table_names = true ) { |
|
984 |
|
985 if ( preg_match( '|[^a-z0-9_]|i', $prefix ) ) { |
|
986 return new WP_Error( 'invalid_db_prefix', 'Invalid database prefix' ); |
|
987 } |
|
988 |
|
989 $old_prefix = is_multisite() ? '' : $prefix; |
|
990 |
|
991 if ( isset( $this->base_prefix ) ) { |
|
992 $old_prefix = $this->base_prefix; |
|
993 } |
|
994 |
|
995 $this->base_prefix = $prefix; |
|
996 |
|
997 if ( $set_table_names ) { |
|
998 foreach ( $this->tables( 'global' ) as $table => $prefixed_table ) { |
|
999 $this->$table = $prefixed_table; |
|
1000 } |
|
1001 |
|
1002 if ( is_multisite() && empty( $this->blogid ) ) { |
|
1003 return $old_prefix; |
|
1004 } |
|
1005 |
|
1006 $this->prefix = $this->get_blog_prefix(); |
|
1007 |
|
1008 foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) { |
|
1009 $this->$table = $prefixed_table; |
|
1010 } |
|
1011 |
|
1012 foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) { |
|
1013 $this->$table = $prefixed_table; |
|
1014 } |
|
1015 } |
|
1016 return $old_prefix; |
|
1017 } |
|
1018 |
|
1019 /** |
|
1020 * Sets blog ID. |
|
1021 * |
|
1022 * @since 3.0.0 |
|
1023 * |
|
1024 * @param int $blog_id |
|
1025 * @param int $network_id Optional. |
|
1026 * @return int Previous blog ID. |
|
1027 */ |
|
1028 public function set_blog_id( $blog_id, $network_id = 0 ) { |
|
1029 if ( ! empty( $network_id ) ) { |
|
1030 $this->siteid = $network_id; |
|
1031 } |
|
1032 |
|
1033 $old_blog_id = $this->blogid; |
|
1034 $this->blogid = $blog_id; |
|
1035 |
|
1036 $this->prefix = $this->get_blog_prefix(); |
|
1037 |
|
1038 foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) { |
|
1039 $this->$table = $prefixed_table; |
|
1040 } |
|
1041 |
|
1042 foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) { |
|
1043 $this->$table = $prefixed_table; |
|
1044 } |
|
1045 |
|
1046 return $old_blog_id; |
|
1047 } |
|
1048 |
|
1049 /** |
|
1050 * Gets blog prefix. |
|
1051 * |
|
1052 * @since 3.0.0 |
|
1053 * |
|
1054 * @param int $blog_id Optional. |
|
1055 * @return string Blog prefix. |
|
1056 */ |
|
1057 public function get_blog_prefix( $blog_id = null ) { |
|
1058 if ( is_multisite() ) { |
|
1059 if ( null === $blog_id ) { |
|
1060 $blog_id = $this->blogid; |
|
1061 } |
|
1062 |
|
1063 $blog_id = (int) $blog_id; |
|
1064 |
|
1065 if ( defined( 'MULTISITE' ) && ( 0 === $blog_id || 1 === $blog_id ) ) { |
|
1066 return $this->base_prefix; |
|
1067 } else { |
|
1068 return $this->base_prefix . $blog_id . '_'; |
|
1069 } |
|
1070 } else { |
|
1071 return $this->base_prefix; |
|
1072 } |
|
1073 } |
|
1074 |
|
1075 /** |
|
1076 * Returns an array of WordPress tables. |
|
1077 * |
|
1078 * Also allows for the `CUSTOM_USER_TABLE` and `CUSTOM_USER_META_TABLE` to override the WordPress users |
|
1079 * and usermeta tables that would otherwise be determined by the prefix. |
|
1080 * |
|
1081 * The `$scope` argument can take one of the following: |
|
1082 * |
|
1083 * - 'all' - returns 'all' and 'global' tables. No old tables are returned. |
|
1084 * - 'blog' - returns the blog-level tables for the queried blog. |
|
1085 * - 'global' - returns the global tables for the installation, returning multisite tables only on multisite. |
|
1086 * - 'ms_global' - returns the multisite global tables, regardless if current installation is multisite. |
|
1087 * - 'old' - returns tables which are deprecated. |
|
1088 * |
|
1089 * @since 3.0.0 |
|
1090 * |
|
1091 * @uses wpdb::$tables |
|
1092 * @uses wpdb::$old_tables |
|
1093 * @uses wpdb::$global_tables |
|
1094 * @uses wpdb::$ms_global_tables |
|
1095 * |
|
1096 * @param string $scope Optional. Possible values include 'all', 'global', 'ms_global', 'blog', |
|
1097 * or 'old' tables. Default 'all'. |
|
1098 * @param bool $prefix Optional. Whether to include table prefixes. If blog prefix is requested, |
|
1099 * then the custom users and usermeta tables will be mapped. Default true. |
|
1100 * @param int $blog_id Optional. The blog_id to prefix. Used only when prefix is requested. |
|
1101 * Defaults to `wpdb::$blogid`. |
|
1102 * @return string[] Table names. When a prefix is requested, the key is the unprefixed table name. |
|
1103 */ |
|
1104 public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) { |
|
1105 switch ( $scope ) { |
|
1106 case 'all': |
|
1107 $tables = array_merge( $this->global_tables, $this->tables ); |
|
1108 if ( is_multisite() ) { |
|
1109 $tables = array_merge( $tables, $this->ms_global_tables ); |
|
1110 } |
|
1111 break; |
|
1112 case 'blog': |
|
1113 $tables = $this->tables; |
|
1114 break; |
|
1115 case 'global': |
|
1116 $tables = $this->global_tables; |
|
1117 if ( is_multisite() ) { |
|
1118 $tables = array_merge( $tables, $this->ms_global_tables ); |
|
1119 } |
|
1120 break; |
|
1121 case 'ms_global': |
|
1122 $tables = $this->ms_global_tables; |
|
1123 break; |
|
1124 case 'old': |
|
1125 $tables = $this->old_tables; |
|
1126 break; |
|
1127 default: |
|
1128 return array(); |
|
1129 } |
|
1130 |
|
1131 if ( $prefix ) { |
|
1132 if ( ! $blog_id ) { |
|
1133 $blog_id = $this->blogid; |
|
1134 } |
|
1135 $blog_prefix = $this->get_blog_prefix( $blog_id ); |
|
1136 $base_prefix = $this->base_prefix; |
|
1137 $global_tables = array_merge( $this->global_tables, $this->ms_global_tables ); |
|
1138 foreach ( $tables as $k => $table ) { |
|
1139 if ( in_array( $table, $global_tables, true ) ) { |
|
1140 $tables[ $table ] = $base_prefix . $table; |
|
1141 } else { |
|
1142 $tables[ $table ] = $blog_prefix . $table; |
|
1143 } |
|
1144 unset( $tables[ $k ] ); |
|
1145 } |
|
1146 |
|
1147 if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) ) { |
|
1148 $tables['users'] = CUSTOM_USER_TABLE; |
|
1149 } |
|
1150 |
|
1151 if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) ) { |
|
1152 $tables['usermeta'] = CUSTOM_USER_META_TABLE; |
|
1153 } |
|
1154 } |
|
1155 |
|
1156 return $tables; |
|
1157 } |
|
1158 |
|
1159 /** |
|
1160 * Selects a database using the current or provided database connection. |
|
1161 * |
|
1162 * The database name will be changed based on the current database connection. |
|
1163 * On failure, the execution will bail and display a DB error. |
|
1164 * |
|
1165 * @since 0.71 |
|
1166 * |
|
1167 * @param string $db Database name. |
|
1168 * @param mysqli|resource $dbh Optional database connection. |
|
1169 */ |
|
1170 public function select( $db, $dbh = null ) { |
|
1171 if ( is_null( $dbh ) ) { |
|
1172 $dbh = $this->dbh; |
|
1173 } |
|
1174 |
|
1175 if ( $this->use_mysqli ) { |
|
1176 $success = mysqli_select_db( $dbh, $db ); |
|
1177 } else { |
|
1178 $success = mysql_select_db( $db, $dbh ); |
|
1179 } |
|
1180 if ( ! $success ) { |
|
1181 $this->ready = false; |
|
1182 if ( ! did_action( 'template_redirect' ) ) { |
|
1183 wp_load_translations_early(); |
|
1184 |
|
1185 $message = '<h1>' . __( 'Cannot select database' ) . "</h1>\n"; |
|
1186 |
|
1187 $message .= '<p>' . sprintf( |
|
1188 /* translators: %s: Database name. */ |
|
1189 __( 'The database server could be connected to (which means your username and password is okay) but the %s database could not be selected.' ), |
|
1190 '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>' |
|
1191 ) . "</p>\n"; |
|
1192 |
|
1193 $message .= "<ul>\n"; |
|
1194 $message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n"; |
|
1195 |
|
1196 $message .= '<li>' . sprintf( |
|
1197 /* translators: 1: Database user, 2: Database name. */ |
|
1198 __( 'Does the user %1$s have permission to use the %2$s database?' ), |
|
1199 '<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '</code>', |
|
1200 '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>' |
|
1201 ) . "</li>\n"; |
|
1202 |
|
1203 $message .= '<li>' . sprintf( |
|
1204 /* translators: %s: Database name. */ |
|
1205 __( 'On some systems the name of your database is prefixed with your username, so it would be like <code>username_%1$s</code>. Could that be the problem?' ), |
|
1206 htmlspecialchars( $db, ENT_QUOTES ) |
|
1207 ) . "</li>\n"; |
|
1208 |
|
1209 $message .= "</ul>\n"; |
|
1210 |
|
1211 $message .= '<p>' . sprintf( |
|
1212 /* translators: %s: Support forums URL. */ |
|
1213 __( 'If you do not know how to set up a database you should <strong>contact your host</strong>. If all else fails you may find help at the <a href="%s">WordPress Support Forums</a>.' ), |
|
1214 __( 'https://wordpress.org/support/forums/' ) |
|
1215 ) . "</p>\n"; |
|
1216 |
|
1217 $this->bail( $message, 'db_select_fail' ); |
|
1218 } |
|
1219 } |
|
1220 } |
|
1221 |
|
1222 /** |
|
1223 * Do not use, deprecated. |
|
1224 * |
|
1225 * Use esc_sql() or wpdb::prepare() instead. |
|
1226 * |
|
1227 * @since 2.8.0 |
|
1228 * @deprecated 3.6.0 Use wpdb::prepare() |
|
1229 * @see wpdb::prepare() |
|
1230 * @see esc_sql() |
|
1231 * |
|
1232 * @param string $string |
|
1233 * @return string |
|
1234 */ |
|
1235 public function _weak_escape( $string ) { |
|
1236 if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) { |
|
1237 _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); |
|
1238 } |
|
1239 return addslashes( $string ); |
|
1240 } |
|
1241 |
|
1242 /** |
|
1243 * Real escape, using mysqli_real_escape_string() or mysql_real_escape_string(). |
|
1244 * |
|
1245 * @since 2.8.0 |
|
1246 * |
|
1247 * @see mysqli_real_escape_string() |
|
1248 * @see mysql_real_escape_string() |
|
1249 * |
|
1250 * @param string $string String to escape. |
|
1251 * @return string Escaped string. |
|
1252 */ |
|
1253 public function _real_escape( $string ) { |
|
1254 if ( ! is_scalar( $string ) ) { |
|
1255 return ''; |
|
1256 } |
|
1257 |
|
1258 if ( $this->dbh ) { |
|
1259 if ( $this->use_mysqli ) { |
|
1260 $escaped = mysqli_real_escape_string( $this->dbh, $string ); |
|
1261 } else { |
|
1262 $escaped = mysql_real_escape_string( $string, $this->dbh ); |
|
1263 } |
|
1264 } else { |
|
1265 $class = get_class( $this ); |
|
1266 |
|
1267 wp_load_translations_early(); |
|
1268 /* translators: %s: Database access abstraction class, usually wpdb or a class extending wpdb. */ |
|
1269 _doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' ); |
|
1270 |
|
1271 $escaped = addslashes( $string ); |
|
1272 } |
|
1273 |
|
1274 return $this->add_placeholder_escape( $escaped ); |
|
1275 } |
|
1276 |
|
1277 /** |
|
1278 * Escapes data. Works on arrays. |
|
1279 * |
|
1280 * @since 2.8.0 |
|
1281 * |
|
1282 * @uses wpdb::_real_escape() |
|
1283 * |
|
1284 * @param string|array $data Data to escape. |
|
1285 * @return string|array Escaped data, in the same type as supplied. |
|
1286 */ |
|
1287 public function _escape( $data ) { |
|
1288 if ( is_array( $data ) ) { |
|
1289 foreach ( $data as $k => $v ) { |
|
1290 if ( is_array( $v ) ) { |
|
1291 $data[ $k ] = $this->_escape( $v ); |
|
1292 } else { |
|
1293 $data[ $k ] = $this->_real_escape( $v ); |
|
1294 } |
|
1295 } |
|
1296 } else { |
|
1297 $data = $this->_real_escape( $data ); |
|
1298 } |
|
1299 |
|
1300 return $data; |
|
1301 } |
|
1302 |
|
1303 /** |
|
1304 * Do not use, deprecated. |
|
1305 * |
|
1306 * Use esc_sql() or wpdb::prepare() instead. |
|
1307 * |
|
1308 * @since 0.71 |
|
1309 * @deprecated 3.6.0 Use wpdb::prepare() |
|
1310 * @see wpdb::prepare() |
|
1311 * @see esc_sql() |
|
1312 * |
|
1313 * @param string|array $data Data to escape. |
|
1314 * @return string|array Escaped data, in the same type as supplied. |
|
1315 */ |
|
1316 public function escape( $data ) { |
|
1317 if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) { |
|
1318 _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); |
|
1319 } |
|
1320 if ( is_array( $data ) ) { |
|
1321 foreach ( $data as $k => $v ) { |
|
1322 if ( is_array( $v ) ) { |
|
1323 $data[ $k ] = $this->escape( $v, 'recursive' ); |
|
1324 } else { |
|
1325 $data[ $k ] = $this->_weak_escape( $v, 'internal' ); |
|
1326 } |
|
1327 } |
|
1328 } else { |
|
1329 $data = $this->_weak_escape( $data, 'internal' ); |
|
1330 } |
|
1331 |
|
1332 return $data; |
|
1333 } |
|
1334 |
|
1335 /** |
|
1336 * Escapes content by reference for insertion into the database, for security. |
|
1337 * |
|
1338 * @uses wpdb::_real_escape() |
|
1339 * |
|
1340 * @since 2.3.0 |
|
1341 * |
|
1342 * @param string $string String to escape. |
|
1343 */ |
|
1344 public function escape_by_ref( &$string ) { |
|
1345 if ( ! is_float( $string ) ) { |
|
1346 $string = $this->_real_escape( $string ); |
|
1347 } |
|
1348 } |
|
1349 |
|
1350 /** |
|
1351 * Prepares a SQL query for safe execution. |
|
1352 * |
|
1353 * Uses sprintf()-like syntax. The following placeholders can be used in the query string: |
|
1354 * |
|
1355 * - %d (integer) |
|
1356 * - %f (float) |
|
1357 * - %s (string) |
|
1358 * |
|
1359 * All placeholders MUST be left unquoted in the query string. A corresponding argument |
|
1360 * MUST be passed for each placeholder. |
|
1361 * |
|
1362 * Note: There is one exception to the above: for compatibility with old behavior, |
|
1363 * numbered or formatted string placeholders (eg, `%1$s`, `%5s`) will not have quotes |
|
1364 * added by this function, so should be passed with appropriate quotes around them. |
|
1365 * |
|
1366 * Literal percentage signs (`%`) in the query string must be written as `%%`. Percentage wildcards |
|
1367 * (for example, to use in LIKE syntax) must be passed via a substitution argument containing |
|
1368 * the complete LIKE string, these cannot be inserted directly in the query string. |
|
1369 * Also see wpdb::esc_like(). |
|
1370 * |
|
1371 * Arguments may be passed as individual arguments to the method, or as a single array |
|
1372 * containing all arguments. A combination of the two is not supported. |
|
1373 * |
|
1374 * Examples: |
|
1375 * |
|
1376 * $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", array( 'foo', 1337, '%bar' ) ); |
|
1377 * $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' ); |
|
1378 * |
|
1379 * @since 2.3.0 |
|
1380 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter |
|
1381 * by updating the function signature. The second parameter was changed |
|
1382 * from `$args` to `...$args`. |
|
1383 * |
|
1384 * @link https://www.php.net/sprintf Description of syntax. |
|
1385 * |
|
1386 * @param string $query Query statement with sprintf()-like placeholders. |
|
1387 * @param array|mixed $args The array of variables to substitute into the query's placeholders |
|
1388 * if being called with an array of arguments, or the first variable |
|
1389 * to substitute into the query's placeholders if being called with |
|
1390 * individual arguments. |
|
1391 * @param mixed ...$args Further variables to substitute into the query's placeholders |
|
1392 * if being called with individual arguments. |
|
1393 * @return string|void Sanitized query string, if there is a query to prepare. |
|
1394 */ |
|
1395 public function prepare( $query, ...$args ) { |
|
1396 if ( is_null( $query ) ) { |
|
1397 return; |
|
1398 } |
|
1399 |
|
1400 // This is not meant to be foolproof -- but it will catch obviously incorrect usage. |
|
1401 if ( strpos( $query, '%' ) === false ) { |
|
1402 wp_load_translations_early(); |
|
1403 _doing_it_wrong( |
|
1404 'wpdb::prepare', |
|
1405 sprintf( |
|
1406 /* translators: %s: wpdb::prepare() */ |
|
1407 __( 'The query argument of %s must have a placeholder.' ), |
|
1408 'wpdb::prepare()' |
|
1409 ), |
|
1410 '3.9.0' |
|
1411 ); |
|
1412 } |
|
1413 |
|
1414 // If args were passed as an array (as in vsprintf), move them up. |
|
1415 $passed_as_array = false; |
|
1416 if ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) ) { |
|
1417 $passed_as_array = true; |
|
1418 $args = $args[0]; |
|
1419 } |
|
1420 |
|
1421 foreach ( $args as $arg ) { |
|
1422 if ( ! is_scalar( $arg ) && ! is_null( $arg ) ) { |
|
1423 wp_load_translations_early(); |
|
1424 _doing_it_wrong( |
|
1425 'wpdb::prepare', |
|
1426 sprintf( |
|
1427 /* translators: %s: Value type. */ |
|
1428 __( 'Unsupported value type (%s).' ), |
|
1429 gettype( $arg ) |
|
1430 ), |
|
1431 '4.8.2' |
|
1432 ); |
|
1433 } |
|
1434 } |
|
1435 |
|
1436 /* |
|
1437 * Specify the formatting allowed in a placeholder. The following are allowed: |
|
1438 * |
|
1439 * - Sign specifier. eg, $+d |
|
1440 * - Numbered placeholders. eg, %1$s |
|
1441 * - Padding specifier, including custom padding characters. eg, %05s, %'#5s |
|
1442 * - Alignment specifier. eg, %05-s |
|
1443 * - Precision specifier. eg, %.2f |
|
1444 */ |
|
1445 $allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?'; |
|
1446 |
|
1447 /* |
|
1448 * If a %s placeholder already has quotes around it, removing the existing quotes and re-inserting them |
|
1449 * ensures the quotes are consistent. |
|
1450 * |
|
1451 * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s, which are frequently |
|
1452 * used in the middle of longer strings, or as table name placeholders. |
|
1453 */ |
|
1454 $query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes. |
|
1455 $query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes. |
|
1456 $query = preg_replace( '/(?<!%)%s/', "'%s'", $query ); // Quote the strings, avoiding escaped strings like %%s. |
|
1457 |
|
1458 $query = preg_replace( "/(?<!%)(%($allowed_format)?f)/", '%\\2F', $query ); // Force floats to be locale-unaware. |
|
1459 |
|
1460 $query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdF]))/", '%%\\1', $query ); // Escape any unescaped percents. |
|
1461 |
|
1462 // Count the number of valid placeholders in the query. |
|
1463 $placeholders = preg_match_all( "/(^|[^%]|(%%)+)%($allowed_format)?[sdF]/", $query, $matches ); |
|
1464 |
|
1465 $args_count = count( $args ); |
|
1466 |
|
1467 if ( $args_count !== $placeholders ) { |
|
1468 if ( 1 === $placeholders && $passed_as_array ) { |
|
1469 // If the passed query only expected one argument, but the wrong number of arguments were sent as an array, bail. |
|
1470 wp_load_translations_early(); |
|
1471 _doing_it_wrong( |
|
1472 'wpdb::prepare', |
|
1473 __( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ), |
|
1474 '4.9.0' |
|
1475 ); |
|
1476 |
|
1477 return; |
|
1478 } else { |
|
1479 /* |
|
1480 * If we don't have the right number of placeholders, but they were passed as individual arguments, |
|
1481 * or we were expecting multiple arguments in an array, throw a warning. |
|
1482 */ |
|
1483 wp_load_translations_early(); |
|
1484 _doing_it_wrong( |
|
1485 'wpdb::prepare', |
|
1486 sprintf( |
|
1487 /* translators: 1: Number of placeholders, 2: Number of arguments passed. */ |
|
1488 __( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ), |
|
1489 $placeholders, |
|
1490 $args_count |
|
1491 ), |
|
1492 '4.8.3' |
|
1493 ); |
|
1494 |
|
1495 /* |
|
1496 * If we don't have enough arguments to match the placeholders, |
|
1497 * return an empty string to avoid a fatal error on PHP 8. |
|
1498 */ |
|
1499 if ( $args_count < $placeholders ) { |
|
1500 $max_numbered_placeholder = ! empty( $matches[3] ) ? max( array_map( 'intval', $matches[3] ) ) : 0; |
|
1501 |
|
1502 if ( ! $max_numbered_placeholder || $args_count < $max_numbered_placeholder ) { |
|
1503 return ''; |
|
1504 } |
|
1505 } |
|
1506 } |
|
1507 } |
|
1508 |
|
1509 array_walk( $args, array( $this, 'escape_by_ref' ) ); |
|
1510 $query = vsprintf( $query, $args ); |
|
1511 |
|
1512 return $this->add_placeholder_escape( $query ); |
|
1513 } |
|
1514 |
|
1515 /** |
|
1516 * First half of escaping for `LIKE` special characters `%` and `_` before preparing for SQL. |
|
1517 * |
|
1518 * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security. |
|
1519 * |
|
1520 * Example Prepared Statement: |
|
1521 * |
|
1522 * $wild = '%'; |
|
1523 * $find = 'only 43% of planets'; |
|
1524 * $like = $wild . $wpdb->esc_like( $find ) . $wild; |
|
1525 * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like ); |
|
1526 * |
|
1527 * Example Escape Chain: |
|
1528 * |
|
1529 * $sql = esc_sql( $wpdb->esc_like( $input ) ); |
|
1530 * |
|
1531 * @since 4.0.0 |
|
1532 * |
|
1533 * @param string $text The raw text to be escaped. The input typed by the user |
|
1534 * should have no extra or deleted slashes. |
|
1535 * @return string Text in the form of a LIKE phrase. The output is not SQL safe. |
|
1536 * Call wpdb::prepare() or wpdb::_real_escape() next. |
|
1537 */ |
|
1538 public function esc_like( $text ) { |
|
1539 return addcslashes( $text, '_%\\' ); |
|
1540 } |
|
1541 |
|
1542 /** |
|
1543 * Prints SQL/DB error. |
|
1544 * |
|
1545 * @since 0.71 |
|
1546 * |
|
1547 * @global array $EZSQL_ERROR Stores error information of query and error string. |
|
1548 * |
|
1549 * @param string $str The error to display. |
|
1550 * @return void|false Void if the showing of errors is enabled, false if disabled. |
|
1551 */ |
|
1552 public function print_error( $str = '' ) { |
|
1553 global $EZSQL_ERROR; |
|
1554 |
|
1555 if ( ! $str ) { |
|
1556 if ( $this->use_mysqli ) { |
|
1557 $str = mysqli_error( $this->dbh ); |
|
1558 } else { |
|
1559 $str = mysql_error( $this->dbh ); |
|
1560 } |
|
1561 } |
|
1562 $EZSQL_ERROR[] = array( |
|
1563 'query' => $this->last_query, |
|
1564 'error_str' => $str, |
|
1565 ); |
|
1566 |
|
1567 if ( $this->suppress_errors ) { |
|
1568 return false; |
|
1569 } |
|
1570 |
|
1571 wp_load_translations_early(); |
|
1572 |
|
1573 $caller = $this->get_caller(); |
|
1574 if ( $caller ) { |
|
1575 /* translators: 1: Database error message, 2: SQL query, 3: Name of the calling function. */ |
|
1576 $error_str = sprintf( __( 'WordPress database error %1$s for query %2$s made by %3$s' ), $str, $this->last_query, $caller ); |
|
1577 } else { |
|
1578 /* translators: 1: Database error message, 2: SQL query. */ |
|
1579 $error_str = sprintf( __( 'WordPress database error %1$s for query %2$s' ), $str, $this->last_query ); |
|
1580 } |
|
1581 |
|
1582 error_log( $error_str ); |
|
1583 |
|
1584 // Are we showing errors? |
|
1585 if ( ! $this->show_errors ) { |
|
1586 return false; |
|
1587 } |
|
1588 |
|
1589 // If there is an error then take note of it. |
|
1590 if ( is_multisite() ) { |
|
1591 $msg = sprintf( |
|
1592 "%s [%s]\n%s\n", |
|
1593 __( 'WordPress database error:' ), |
|
1594 $str, |
|
1595 $this->last_query |
|
1596 ); |
|
1597 |
|
1598 if ( defined( 'ERRORLOGFILE' ) ) { |
|
1599 error_log( $msg, 3, ERRORLOGFILE ); |
|
1600 } |
|
1601 if ( defined( 'DIEONDBERROR' ) ) { |
|
1602 wp_die( $msg ); |
|
1603 } |
|
1604 } else { |
|
1605 $str = htmlspecialchars( $str, ENT_QUOTES ); |
|
1606 $query = htmlspecialchars( $this->last_query, ENT_QUOTES ); |
|
1607 |
|
1608 printf( |
|
1609 '<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>', |
|
1610 __( 'WordPress database error:' ), |
|
1611 $str, |
|
1612 $query |
|
1613 ); |
|
1614 } |
|
1615 } |
|
1616 |
|
1617 /** |
|
1618 * Enables showing of database errors. |
|
1619 * |
|
1620 * This function should be used only to enable showing of errors. |
|
1621 * wpdb::hide_errors() should be used instead for hiding errors. |
|
1622 * |
|
1623 * @since 0.71 |
|
1624 * |
|
1625 * @see wpdb::hide_errors() |
|
1626 * |
|
1627 * @param bool $show Optional. Whether to show errors. Default true. |
|
1628 * @return bool Whether showing of errors was previously active. |
|
1629 */ |
|
1630 public function show_errors( $show = true ) { |
|
1631 $errors = $this->show_errors; |
|
1632 $this->show_errors = $show; |
|
1633 return $errors; |
|
1634 } |
|
1635 |
|
1636 /** |
|
1637 * Disables showing of database errors. |
|
1638 * |
|
1639 * By default database errors are not shown. |
|
1640 * |
|
1641 * @since 0.71 |
|
1642 * |
|
1643 * @see wpdb::show_errors() |
|
1644 * |
|
1645 * @return bool Whether showing of errors was previously active. |
|
1646 */ |
|
1647 public function hide_errors() { |
|
1648 $show = $this->show_errors; |
|
1649 $this->show_errors = false; |
|
1650 return $show; |
|
1651 } |
|
1652 |
|
1653 /** |
|
1654 * Enables or disables suppressing of database errors. |
|
1655 * |
|
1656 * By default database errors are suppressed. |
|
1657 * |
|
1658 * @since 2.5.0 |
|
1659 * |
|
1660 * @see wpdb::hide_errors() |
|
1661 * |
|
1662 * @param bool $suppress Optional. Whether to suppress errors. Default true. |
|
1663 * @return bool Whether suppressing of errors was previously active. |
|
1664 */ |
|
1665 public function suppress_errors( $suppress = true ) { |
|
1666 $errors = $this->suppress_errors; |
|
1667 $this->suppress_errors = (bool) $suppress; |
|
1668 return $errors; |
|
1669 } |
|
1670 |
|
1671 /** |
|
1672 * Kills cached query results. |
|
1673 * |
|
1674 * @since 0.71 |
|
1675 */ |
|
1676 public function flush() { |
|
1677 $this->last_result = array(); |
|
1678 $this->col_info = null; |
|
1679 $this->last_query = null; |
|
1680 $this->rows_affected = 0; |
|
1681 $this->num_rows = 0; |
|
1682 $this->last_error = ''; |
|
1683 |
|
1684 if ( $this->use_mysqli && $this->result instanceof mysqli_result ) { |
|
1685 mysqli_free_result( $this->result ); |
|
1686 $this->result = null; |
|
1687 |
|
1688 // Sanity check before using the handle. |
|
1689 if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) { |
|
1690 return; |
|
1691 } |
|
1692 |
|
1693 // Clear out any results from a multi-query. |
|
1694 while ( mysqli_more_results( $this->dbh ) ) { |
|
1695 mysqli_next_result( $this->dbh ); |
|
1696 } |
|
1697 } elseif ( is_resource( $this->result ) ) { |
|
1698 mysql_free_result( $this->result ); |
|
1699 } |
|
1700 } |
|
1701 |
|
1702 /** |
|
1703 * Connects to and selects database. |
|
1704 * |
|
1705 * If `$allow_bail` is false, the lack of database connection will need to be handled manually. |
|
1706 * |
|
1707 * @since 3.0.0 |
|
1708 * @since 3.9.0 $allow_bail parameter added. |
|
1709 * |
|
1710 * @param bool $allow_bail Optional. Allows the function to bail. Default true. |
|
1711 * @return bool True with a successful connection, false on failure. |
|
1712 */ |
|
1713 public function db_connect( $allow_bail = true ) { |
|
1714 $this->is_mysql = true; |
|
1715 |
|
1716 /* |
|
1717 * Deprecated in 3.9+ when using MySQLi. No equivalent |
|
1718 * $new_link parameter exists for mysqli_* functions. |
|
1719 */ |
|
1720 $new_link = defined( 'MYSQL_NEW_LINK' ) ? MYSQL_NEW_LINK : true; |
|
1721 $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0; |
|
1722 |
|
1723 if ( $this->use_mysqli ) { |
|
1724 /* |
|
1725 * Set the MySQLi error reporting off because WordPress handles its own. |
|
1726 * This is due to the default value change from `MYSQLI_REPORT_OFF` |
|
1727 * to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1. |
|
1728 */ |
|
1729 mysqli_report( MYSQLI_REPORT_OFF ); |
|
1730 |
|
1731 $this->dbh = mysqli_init(); |
|
1732 |
|
1733 $host = $this->dbhost; |
|
1734 $port = null; |
|
1735 $socket = null; |
|
1736 $is_ipv6 = false; |
|
1737 |
|
1738 $host_data = $this->parse_db_host( $this->dbhost ); |
|
1739 if ( $host_data ) { |
|
1740 list( $host, $port, $socket, $is_ipv6 ) = $host_data; |
|
1741 } |
|
1742 |
|
1743 /* |
|
1744 * If using the `mysqlnd` library, the IPv6 address needs to be enclosed |
|
1745 * in square brackets, whereas it doesn't while using the `libmysqlclient` library. |
|
1746 * @see https://bugs.php.net/bug.php?id=67563 |
|
1747 */ |
|
1748 if ( $is_ipv6 && extension_loaded( 'mysqlnd' ) ) { |
|
1749 $host = "[$host]"; |
|
1750 } |
|
1751 |
|
1752 if ( WP_DEBUG ) { |
|
1753 mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags ); |
|
1754 } else { |
|
1755 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged |
|
1756 @mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags ); |
|
1757 } |
|
1758 |
|
1759 if ( $this->dbh->connect_errno ) { |
|
1760 $this->dbh = null; |
|
1761 |
|
1762 /* |
|
1763 * It's possible ext/mysqli is misconfigured. Fall back to ext/mysql if: |
|
1764 * - We haven't previously connected, and |
|
1765 * - WP_USE_EXT_MYSQL isn't set to false, and |
|
1766 * - ext/mysql is loaded. |
|
1767 */ |
|
1768 $attempt_fallback = true; |
|
1769 |
|
1770 if ( $this->has_connected ) { |
|
1771 $attempt_fallback = false; |
|
1772 } elseif ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) { |
|
1773 $attempt_fallback = false; |
|
1774 } elseif ( ! function_exists( 'mysql_connect' ) ) { |
|
1775 $attempt_fallback = false; |
|
1776 } |
|
1777 |
|
1778 if ( $attempt_fallback ) { |
|
1779 $this->use_mysqli = false; |
|
1780 return $this->db_connect( $allow_bail ); |
|
1781 } |
|
1782 } |
|
1783 } else { |
|
1784 if ( WP_DEBUG ) { |
|
1785 $this->dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags ); |
|
1786 } else { |
|
1787 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged |
|
1788 $this->dbh = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags ); |
|
1789 } |
|
1790 } |
|
1791 |
|
1792 if ( ! $this->dbh && $allow_bail ) { |
|
1793 wp_load_translations_early(); |
|
1794 |
|
1795 // Load custom DB error template, if present. |
|
1796 if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) { |
|
1797 require_once WP_CONTENT_DIR . '/db-error.php'; |
|
1798 die(); |
|
1799 } |
|
1800 |
|
1801 $message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n"; |
|
1802 |
|
1803 $message .= '<p>' . sprintf( |
|
1804 /* translators: 1: wp-config.php, 2: Database host. */ |
|
1805 __( 'This either means that the username and password information in your %1$s file is incorrect or that contact with the database server at %2$s could not be established. This could mean your host’s database server is down.' ), |
|
1806 '<code>wp-config.php</code>', |
|
1807 '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>' |
|
1808 ) . "</p>\n"; |
|
1809 |
|
1810 $message .= "<ul>\n"; |
|
1811 $message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n"; |
|
1812 $message .= '<li>' . __( 'Are you sure you have typed the correct hostname?' ) . "</li>\n"; |
|
1813 $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n"; |
|
1814 $message .= "</ul>\n"; |
|
1815 |
|
1816 $message .= '<p>' . sprintf( |
|
1817 /* translators: %s: Support forums URL. */ |
|
1818 __( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress Support Forums</a>.' ), |
|
1819 __( 'https://wordpress.org/support/forums/' ) |
|
1820 ) . "</p>\n"; |
|
1821 |
|
1822 $this->bail( $message, 'db_connect_fail' ); |
|
1823 |
|
1824 return false; |
|
1825 } elseif ( $this->dbh ) { |
|
1826 if ( ! $this->has_connected ) { |
|
1827 $this->init_charset(); |
|
1828 } |
|
1829 |
|
1830 $this->has_connected = true; |
|
1831 |
|
1832 $this->set_charset( $this->dbh ); |
|
1833 |
|
1834 $this->ready = true; |
|
1835 $this->set_sql_mode(); |
|
1836 $this->select( $this->dbname, $this->dbh ); |
|
1837 |
|
1838 return true; |
|
1839 } |
|
1840 |
|
1841 return false; |
|
1842 } |
|
1843 |
|
1844 /** |
|
1845 * Parses the DB_HOST setting to interpret it for mysqli_real_connect(). |
|
1846 * |
|
1847 * mysqli_real_connect() doesn't support the host param including a port or socket |
|
1848 * like mysql_connect() does. This duplicates how mysql_connect() detects a port |
|
1849 * and/or socket file. |
|
1850 * |
|
1851 * @since 4.9.0 |
|
1852 * |
|
1853 * @param string $host The DB_HOST setting to parse. |
|
1854 * @return array|false { |
|
1855 * Array containing the host, the port, the socket and |
|
1856 * whether it is an IPv6 address, in that order. |
|
1857 * False if the host couldn't be parsed. |
|
1858 * |
|
1859 * @type string $0 Host name. |
|
1860 * @type string|null $1 Port. |
|
1861 * @type string|null $2 Socket. |
|
1862 * @type bool $3 Whether it is an IPv6 address. |
|
1863 * } |
|
1864 */ |
|
1865 public function parse_db_host( $host ) { |
|
1866 $port = null; |
|
1867 $socket = null; |
|
1868 $is_ipv6 = false; |
|
1869 |
|
1870 // First peel off the socket parameter from the right, if it exists. |
|
1871 $socket_pos = strpos( $host, ':/' ); |
|
1872 if ( false !== $socket_pos ) { |
|
1873 $socket = substr( $host, $socket_pos + 1 ); |
|
1874 $host = substr( $host, 0, $socket_pos ); |
|
1875 } |
|
1876 |
|
1877 // We need to check for an IPv6 address first. |
|
1878 // An IPv6 address will always contain at least two colons. |
|
1879 if ( substr_count( $host, ':' ) > 1 ) { |
|
1880 $pattern = '#^(?:\[)?(?P<host>[0-9a-fA-F:]+)(?:\]:(?P<port>[\d]+))?#'; |
|
1881 $is_ipv6 = true; |
|
1882 } else { |
|
1883 // We seem to be dealing with an IPv4 address. |
|
1884 $pattern = '#^(?P<host>[^:/]*)(?::(?P<port>[\d]+))?#'; |
|
1885 } |
|
1886 |
|
1887 $matches = array(); |
|
1888 $result = preg_match( $pattern, $host, $matches ); |
|
1889 |
|
1890 if ( 1 !== $result ) { |
|
1891 // Couldn't parse the address, bail. |
|
1892 return false; |
|
1893 } |
|
1894 |
|
1895 $host = ''; |
|
1896 foreach ( array( 'host', 'port' ) as $component ) { |
|
1897 if ( ! empty( $matches[ $component ] ) ) { |
|
1898 $$component = $matches[ $component ]; |
|
1899 } |
|
1900 } |
|
1901 |
|
1902 return array( $host, $port, $socket, $is_ipv6 ); |
|
1903 } |
|
1904 |
|
1905 /** |
|
1906 * Checks that the connection to the database is still up. If not, try to reconnect. |
|
1907 * |
|
1908 * If this function is unable to reconnect, it will forcibly die, or if called |
|
1909 * after the {@see 'template_redirect'} hook has been fired, return false instead. |
|
1910 * |
|
1911 * If `$allow_bail` is false, the lack of database connection will need to be handled manually. |
|
1912 * |
|
1913 * @since 3.9.0 |
|
1914 * |
|
1915 * @param bool $allow_bail Optional. Allows the function to bail. Default true. |
|
1916 * @return bool|void True if the connection is up. |
|
1917 */ |
|
1918 public function check_connection( $allow_bail = true ) { |
|
1919 if ( $this->use_mysqli ) { |
|
1920 if ( ! empty( $this->dbh ) && mysqli_ping( $this->dbh ) ) { |
|
1921 return true; |
|
1922 } |
|
1923 } else { |
|
1924 if ( ! empty( $this->dbh ) && mysql_ping( $this->dbh ) ) { |
|
1925 return true; |
|
1926 } |
|
1927 } |
|
1928 |
|
1929 $error_reporting = false; |
|
1930 |
|
1931 // Disable warnings, as we don't want to see a multitude of "unable to connect" messages. |
|
1932 if ( WP_DEBUG ) { |
|
1933 $error_reporting = error_reporting(); |
|
1934 error_reporting( $error_reporting & ~E_WARNING ); |
|
1935 } |
|
1936 |
|
1937 for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) { |
|
1938 // On the last try, re-enable warnings. We want to see a single instance |
|
1939 // of the "unable to connect" message on the bail() screen, if it appears. |
|
1940 if ( $this->reconnect_retries === $tries && WP_DEBUG ) { |
|
1941 error_reporting( $error_reporting ); |
|
1942 } |
|
1943 |
|
1944 if ( $this->db_connect( false ) ) { |
|
1945 if ( $error_reporting ) { |
|
1946 error_reporting( $error_reporting ); |
|
1947 } |
|
1948 |
|
1949 return true; |
|
1950 } |
|
1951 |
|
1952 sleep( 1 ); |
|
1953 } |
|
1954 |
|
1955 // If template_redirect has already happened, it's too late for wp_die()/dead_db(). |
|
1956 // Let's just return and hope for the best. |
|
1957 if ( did_action( 'template_redirect' ) ) { |
|
1958 return false; |
|
1959 } |
|
1960 |
|
1961 if ( ! $allow_bail ) { |
|
1962 return false; |
|
1963 } |
|
1964 |
|
1965 wp_load_translations_early(); |
|
1966 |
|
1967 $message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n"; |
|
1968 |
|
1969 $message .= '<p>' . sprintf( |
|
1970 /* translators: %s: Database host. */ |
|
1971 __( 'This means that the contact with the database server at %s was lost. This could mean your host’s database server is down.' ), |
|
1972 '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>' |
|
1973 ) . "</p>\n"; |
|
1974 |
|
1975 $message .= "<ul>\n"; |
|
1976 $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n"; |
|
1977 $message .= '<li>' . __( 'Are you sure the database server is not under particularly heavy load?' ) . "</li>\n"; |
|
1978 $message .= "</ul>\n"; |
|
1979 |
|
1980 $message .= '<p>' . sprintf( |
|
1981 /* translators: %s: Support forums URL. */ |
|
1982 __( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress Support Forums</a>.' ), |
|
1983 __( 'https://wordpress.org/support/forums/' ) |
|
1984 ) . "</p>\n"; |
|
1985 |
|
1986 // We weren't able to reconnect, so we better bail. |
|
1987 $this->bail( $message, 'db_connect_fail' ); |
|
1988 |
|
1989 // Call dead_db() if bail didn't die, because this database is no more. |
|
1990 // It has ceased to be (at least temporarily). |
|
1991 dead_db(); |
|
1992 } |
|
1993 |
|
1994 /** |
|
1995 * Performs a database query, using current database connection. |
|
1996 * |
|
1997 * More information can be found on the documentation page. |
|
1998 * |
|
1999 * @since 0.71 |
|
2000 * |
|
2001 * @link https://developer.wordpress.org/reference/classes/wpdb/ |
|
2002 * |
|
2003 * @param string $query Database query. |
|
2004 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows |
|
2005 * affected/selected for all other queries. Boolean false on error. |
|
2006 */ |
|
2007 public function query( $query ) { |
|
2008 if ( ! $this->ready ) { |
|
2009 $this->check_current_query = true; |
|
2010 return false; |
|
2011 } |
|
2012 |
|
2013 /** |
|
2014 * Filters the database query. |
|
2015 * |
|
2016 * Some queries are made before the plugins have been loaded, |
|
2017 * and thus cannot be filtered with this method. |
|
2018 * |
|
2019 * @since 2.1.0 |
|
2020 * |
|
2021 * @param string $query Database query. |
|
2022 */ |
|
2023 $query = apply_filters( 'query', $query ); |
|
2024 |
|
2025 if ( ! $query ) { |
|
2026 $this->insert_id = 0; |
|
2027 return false; |
|
2028 } |
|
2029 |
|
2030 $this->flush(); |
|
2031 |
|
2032 // Log how the function was called. |
|
2033 $this->func_call = "\$db->query(\"$query\")"; |
|
2034 |
|
2035 // If we're writing to the database, make sure the query will write safely. |
|
2036 if ( $this->check_current_query && ! $this->check_ascii( $query ) ) { |
|
2037 $stripped_query = $this->strip_invalid_text_from_query( $query ); |
|
2038 // strip_invalid_text_from_query() can perform queries, so we need |
|
2039 // to flush again, just to make sure everything is clear. |
|
2040 $this->flush(); |
|
2041 if ( $stripped_query !== $query ) { |
|
2042 $this->insert_id = 0; |
|
2043 $this->last_query = $query; |
|
2044 |
|
2045 wp_load_translations_early(); |
|
2046 |
|
2047 $this->last_error = __( 'WordPress database error: Could not perform query because it contains invalid data.' ); |
|
2048 |
|
2049 return false; |
|
2050 } |
|
2051 } |
|
2052 |
|
2053 $this->check_current_query = true; |
|
2054 |
|
2055 // Keep track of the last query for debug. |
|
2056 $this->last_query = $query; |
|
2057 |
|
2058 $this->_do_query( $query ); |
|
2059 |
|
2060 // Database server has gone away, try to reconnect. |
|
2061 $mysql_errno = 0; |
|
2062 if ( ! empty( $this->dbh ) ) { |
|
2063 if ( $this->use_mysqli ) { |
|
2064 if ( $this->dbh instanceof mysqli ) { |
|
2065 $mysql_errno = mysqli_errno( $this->dbh ); |
|
2066 } else { |
|
2067 // $dbh is defined, but isn't a real connection. |
|
2068 // Something has gone horribly wrong, let's try a reconnect. |
|
2069 $mysql_errno = 2006; |
|
2070 } |
|
2071 } else { |
|
2072 if ( is_resource( $this->dbh ) ) { |
|
2073 $mysql_errno = mysql_errno( $this->dbh ); |
|
2074 } else { |
|
2075 $mysql_errno = 2006; |
|
2076 } |
|
2077 } |
|
2078 } |
|
2079 |
|
2080 if ( empty( $this->dbh ) || 2006 === $mysql_errno ) { |
|
2081 if ( $this->check_connection() ) { |
|
2082 $this->_do_query( $query ); |
|
2083 } else { |
|
2084 $this->insert_id = 0; |
|
2085 return false; |
|
2086 } |
|
2087 } |
|
2088 |
|
2089 // If there is an error then take note of it. |
|
2090 if ( $this->use_mysqli ) { |
|
2091 if ( $this->dbh instanceof mysqli ) { |
|
2092 $this->last_error = mysqli_error( $this->dbh ); |
|
2093 } else { |
|
2094 $this->last_error = __( 'Unable to retrieve the error message from MySQL' ); |
|
2095 } |
|
2096 } else { |
|
2097 if ( is_resource( $this->dbh ) ) { |
|
2098 $this->last_error = mysql_error( $this->dbh ); |
|
2099 } else { |
|
2100 $this->last_error = __( 'Unable to retrieve the error message from MySQL' ); |
|
2101 } |
|
2102 } |
|
2103 |
|
2104 if ( $this->last_error ) { |
|
2105 // Clear insert_id on a subsequent failed insert. |
|
2106 if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) { |
|
2107 $this->insert_id = 0; |
|
2108 } |
|
2109 |
|
2110 $this->print_error(); |
|
2111 return false; |
|
2112 } |
|
2113 |
|
2114 if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) { |
|
2115 $return_val = $this->result; |
|
2116 } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) { |
|
2117 if ( $this->use_mysqli ) { |
|
2118 $this->rows_affected = mysqli_affected_rows( $this->dbh ); |
|
2119 } else { |
|
2120 $this->rows_affected = mysql_affected_rows( $this->dbh ); |
|
2121 } |
|
2122 // Take note of the insert_id. |
|
2123 if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) { |
|
2124 if ( $this->use_mysqli ) { |
|
2125 $this->insert_id = mysqli_insert_id( $this->dbh ); |
|
2126 } else { |
|
2127 $this->insert_id = mysql_insert_id( $this->dbh ); |
|
2128 } |
|
2129 } |
|
2130 // Return number of rows affected. |
|
2131 $return_val = $this->rows_affected; |
|
2132 } else { |
|
2133 $num_rows = 0; |
|
2134 if ( $this->use_mysqli && $this->result instanceof mysqli_result ) { |
|
2135 while ( $row = mysqli_fetch_object( $this->result ) ) { |
|
2136 $this->last_result[ $num_rows ] = $row; |
|
2137 $num_rows++; |
|
2138 } |
|
2139 } elseif ( is_resource( $this->result ) ) { |
|
2140 while ( $row = mysql_fetch_object( $this->result ) ) { |
|
2141 $this->last_result[ $num_rows ] = $row; |
|
2142 $num_rows++; |
|
2143 } |
|
2144 } |
|
2145 |
|
2146 // Log and return the number of rows selected. |
|
2147 $this->num_rows = $num_rows; |
|
2148 $return_val = $num_rows; |
|
2149 } |
|
2150 |
|
2151 return $return_val; |
|
2152 } |
|
2153 |
|
2154 /** |
|
2155 * Internal function to perform the mysql_query() call. |
|
2156 * |
|
2157 * @since 3.9.0 |
|
2158 * |
|
2159 * @see wpdb::query() |
|
2160 * |
|
2161 * @param string $query The query to run. |
|
2162 */ |
|
2163 private function _do_query( $query ) { |
|
2164 if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { |
|
2165 $this->timer_start(); |
|
2166 } |
|
2167 |
|
2168 if ( ! empty( $this->dbh ) && $this->use_mysqli ) { |
|
2169 $this->result = mysqli_query( $this->dbh, $query ); |
|
2170 } elseif ( ! empty( $this->dbh ) ) { |
|
2171 $this->result = mysql_query( $query, $this->dbh ); |
|
2172 } |
|
2173 $this->num_queries++; |
|
2174 |
|
2175 if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { |
|
2176 $this->log_query( |
|
2177 $query, |
|
2178 $this->timer_stop(), |
|
2179 $this->get_caller(), |
|
2180 $this->time_start, |
|
2181 array() |
|
2182 ); |
|
2183 } |
|
2184 } |
|
2185 |
|
2186 /** |
|
2187 * Logs query data. |
|
2188 * |
|
2189 * @since 5.3.0 |
|
2190 * |
|
2191 * @param string $query The query's SQL. |
|
2192 * @param float $query_time Total time spent on the query, in seconds. |
|
2193 * @param string $query_callstack Comma-separated list of the calling functions. |
|
2194 * @param float $query_start Unix timestamp of the time at the start of the query. |
|
2195 * @param array $query_data Custom query data. |
|
2196 */ |
|
2197 public function log_query( $query, $query_time, $query_callstack, $query_start, $query_data ) { |
|
2198 /** |
|
2199 * Filters the custom data to log alongside a query. |
|
2200 * |
|
2201 * Caution should be used when modifying any of this data, it is recommended that any additional |
|
2202 * information you need to store about a query be added as a new associative array element. |
|
2203 * |
|
2204 * @since 5.3.0 |
|
2205 * |
|
2206 * @param array $query_data Custom query data. |
|
2207 * @param string $query The query's SQL. |
|
2208 * @param float $query_time Total time spent on the query, in seconds. |
|
2209 * @param string $query_callstack Comma-separated list of the calling functions. |
|
2210 * @param float $query_start Unix timestamp of the time at the start of the query. |
|
2211 */ |
|
2212 $query_data = apply_filters( 'log_query_custom_data', $query_data, $query, $query_time, $query_callstack, $query_start ); |
|
2213 |
|
2214 $this->queries[] = array( |
|
2215 $query, |
|
2216 $query_time, |
|
2217 $query_callstack, |
|
2218 $query_start, |
|
2219 $query_data, |
|
2220 ); |
|
2221 } |
|
2222 |
|
2223 /** |
|
2224 * Generates and returns a placeholder escape string for use in queries returned by ::prepare(). |
|
2225 * |
|
2226 * @since 4.8.3 |
|
2227 * |
|
2228 * @return string String to escape placeholders. |
|
2229 */ |
|
2230 public function placeholder_escape() { |
|
2231 static $placeholder; |
|
2232 |
|
2233 if ( ! $placeholder ) { |
|
2234 // If ext/hash is not present, compat.php's hash_hmac() does not support sha256. |
|
2235 $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1'; |
|
2236 // Old WP installs may not have AUTH_SALT defined. |
|
2237 $salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand(); |
|
2238 |
|
2239 $placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}'; |
|
2240 } |
|
2241 |
|
2242 /* |
|
2243 * Add the filter to remove the placeholder escaper. Uses priority 0, so that anything |
|
2244 * else attached to this filter will receive the query with the placeholder string removed. |
|
2245 */ |
|
2246 if ( false === has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) { |
|
2247 add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 ); |
|
2248 } |
|
2249 |
|
2250 return $placeholder; |
|
2251 } |
|
2252 |
|
2253 /** |
|
2254 * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder. |
|
2255 * |
|
2256 * @since 4.8.3 |
|
2257 * |
|
2258 * @param string $query The query to escape. |
|
2259 * @return string The query with the placeholder escape string inserted where necessary. |
|
2260 */ |
|
2261 public function add_placeholder_escape( $query ) { |
|
2262 /* |
|
2263 * To prevent returning anything that even vaguely resembles a placeholder, |
|
2264 * we clobber every % we can find. |
|
2265 */ |
|
2266 return str_replace( '%', $this->placeholder_escape(), $query ); |
|
2267 } |
|
2268 |
|
2269 /** |
|
2270 * Removes the placeholder escape strings from a query. |
|
2271 * |
|
2272 * @since 4.8.3 |
|
2273 * |
|
2274 * @param string $query The query from which the placeholder will be removed. |
|
2275 * @return string The query with the placeholder removed. |
|
2276 */ |
|
2277 public function remove_placeholder_escape( $query ) { |
|
2278 return str_replace( $this->placeholder_escape(), '%', $query ); |
|
2279 } |
|
2280 |
|
2281 /** |
|
2282 * Inserts a row into the table. |
|
2283 * |
|
2284 * Examples: |
|
2285 * |
|
2286 * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) ) |
|
2287 * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) ) |
|
2288 * |
|
2289 * @since 2.5.0 |
|
2290 * |
|
2291 * @see wpdb::prepare() |
|
2292 * @see wpdb::$field_types |
|
2293 * @see wp_set_wpdb_vars() |
|
2294 * |
|
2295 * @param string $table Table name. |
|
2296 * @param array $data Data to insert (in column => value pairs). |
|
2297 * Both $data columns and $data values should be "raw" (neither should be SQL escaped). |
|
2298 * Sending a null value will cause the column to be set to NULL - the corresponding |
|
2299 * format is ignored in this case. |
|
2300 * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. |
|
2301 * If string, that format will be used for all of the values in $data. |
|
2302 * A format is one of '%d', '%f', '%s' (integer, float, string). |
|
2303 * If omitted, all values in $data will be treated as strings unless otherwise |
|
2304 * specified in wpdb::$field_types. |
|
2305 * @return int|false The number of rows inserted, or false on error. |
|
2306 */ |
|
2307 public function insert( $table, $data, $format = null ) { |
|
2308 return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' ); |
|
2309 } |
|
2310 |
|
2311 /** |
|
2312 * Replaces a row in the table. |
|
2313 * |
|
2314 * Examples: |
|
2315 * |
|
2316 * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 'bar' ) ) |
|
2317 * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) ) |
|
2318 * |
|
2319 * @since 3.0.0 |
|
2320 * |
|
2321 * @see wpdb::prepare() |
|
2322 * @see wpdb::$field_types |
|
2323 * @see wp_set_wpdb_vars() |
|
2324 * |
|
2325 * @param string $table Table name. |
|
2326 * @param array $data Data to insert (in column => value pairs). |
|
2327 * Both $data columns and $data values should be "raw" (neither should be SQL escaped). |
|
2328 * Sending a null value will cause the column to be set to NULL - the corresponding |
|
2329 * format is ignored in this case. |
|
2330 * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. |
|
2331 * If string, that format will be used for all of the values in $data. |
|
2332 * A format is one of '%d', '%f', '%s' (integer, float, string). |
|
2333 * If omitted, all values in $data will be treated as strings unless otherwise |
|
2334 * specified in wpdb::$field_types. |
|
2335 * @return int|false The number of rows affected, or false on error. |
|
2336 */ |
|
2337 public function replace( $table, $data, $format = null ) { |
|
2338 return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' ); |
|
2339 } |
|
2340 |
|
2341 /** |
|
2342 * Helper function for insert and replace. |
|
2343 * |
|
2344 * Runs an insert or replace query based on $type argument. |
|
2345 * |
|
2346 * @since 3.0.0 |
|
2347 * |
|
2348 * @see wpdb::prepare() |
|
2349 * @see wpdb::$field_types |
|
2350 * @see wp_set_wpdb_vars() |
|
2351 * |
|
2352 * @param string $table Table name. |
|
2353 * @param array $data Data to insert (in column => value pairs). |
|
2354 * Both $data columns and $data values should be "raw" (neither should be SQL escaped). |
|
2355 * Sending a null value will cause the column to be set to NULL - the corresponding |
|
2356 * format is ignored in this case. |
|
2357 * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. |
|
2358 * If string, that format will be used for all of the values in $data. |
|
2359 * A format is one of '%d', '%f', '%s' (integer, float, string). |
|
2360 * If omitted, all values in $data will be treated as strings unless otherwise |
|
2361 * specified in wpdb::$field_types. |
|
2362 * @param string $type Optional. Type of operation. Possible values include 'INSERT' or 'REPLACE'. |
|
2363 * Default 'INSERT'. |
|
2364 * @return int|false The number of rows affected, or false on error. |
|
2365 */ |
|
2366 public function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) { |
|
2367 $this->insert_id = 0; |
|
2368 |
|
2369 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ), true ) ) { |
|
2370 return false; |
|
2371 } |
|
2372 |
|
2373 $data = $this->process_fields( $table, $data, $format ); |
|
2374 if ( false === $data ) { |
|
2375 return false; |
|
2376 } |
|
2377 |
|
2378 $formats = array(); |
|
2379 $values = array(); |
|
2380 foreach ( $data as $value ) { |
|
2381 if ( is_null( $value['value'] ) ) { |
|
2382 $formats[] = 'NULL'; |
|
2383 continue; |
|
2384 } |
|
2385 |
|
2386 $formats[] = $value['format']; |
|
2387 $values[] = $value['value']; |
|
2388 } |
|
2389 |
|
2390 $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`'; |
|
2391 $formats = implode( ', ', $formats ); |
|
2392 |
|
2393 $sql = "$type INTO `$table` ($fields) VALUES ($formats)"; |
|
2394 |
|
2395 $this->check_current_query = false; |
|
2396 return $this->query( $this->prepare( $sql, $values ) ); |
|
2397 } |
|
2398 |
|
2399 /** |
|
2400 * Updates a row in the table. |
|
2401 * |
|
2402 * Examples: |
|
2403 * |
|
2404 * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 'bar' ), array( 'ID' => 1 ) ) |
|
2405 * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( 'ID' => 1 ), array( '%s', '%d' ), array( '%d' ) ) |
|
2406 * |
|
2407 * @since 2.5.0 |
|
2408 * |
|
2409 * @see wpdb::prepare() |
|
2410 * @see wpdb::$field_types |
|
2411 * @see wp_set_wpdb_vars() |
|
2412 * |
|
2413 * @param string $table Table name. |
|
2414 * @param array $data Data to update (in column => value pairs). |
|
2415 * Both $data columns and $data values should be "raw" (neither should be SQL escaped). |
|
2416 * Sending a null value will cause the column to be set to NULL - the corresponding |
|
2417 * format is ignored in this case. |
|
2418 * @param array $where A named array of WHERE clauses (in column => value pairs). |
|
2419 * Multiple clauses will be joined with ANDs. |
|
2420 * Both $where columns and $where values should be "raw". |
|
2421 * Sending a null value will create an IS NULL comparison - the corresponding |
|
2422 * format will be ignored in this case. |
|
2423 * @param array|string $format Optional. An array of formats to be mapped to each of the values in $data. |
|
2424 * If string, that format will be used for all of the values in $data. |
|
2425 * A format is one of '%d', '%f', '%s' (integer, float, string). |
|
2426 * If omitted, all values in $data will be treated as strings unless otherwise |
|
2427 * specified in wpdb::$field_types. |
|
2428 * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where. |
|
2429 * If string, that format will be used for all of the items in $where. |
|
2430 * A format is one of '%d', '%f', '%s' (integer, float, string). |
|
2431 * If omitted, all values in $where will be treated as strings. |
|
2432 * @return int|false The number of rows updated, or false on error. |
|
2433 */ |
|
2434 public function update( $table, $data, $where, $format = null, $where_format = null ) { |
|
2435 if ( ! is_array( $data ) || ! is_array( $where ) ) { |
|
2436 return false; |
|
2437 } |
|
2438 |
|
2439 $data = $this->process_fields( $table, $data, $format ); |
|
2440 if ( false === $data ) { |
|
2441 return false; |
|
2442 } |
|
2443 $where = $this->process_fields( $table, $where, $where_format ); |
|
2444 if ( false === $where ) { |
|
2445 return false; |
|
2446 } |
|
2447 |
|
2448 $fields = array(); |
|
2449 $conditions = array(); |
|
2450 $values = array(); |
|
2451 foreach ( $data as $field => $value ) { |
|
2452 if ( is_null( $value['value'] ) ) { |
|
2453 $fields[] = "`$field` = NULL"; |
|
2454 continue; |
|
2455 } |
|
2456 |
|
2457 $fields[] = "`$field` = " . $value['format']; |
|
2458 $values[] = $value['value']; |
|
2459 } |
|
2460 foreach ( $where as $field => $value ) { |
|
2461 if ( is_null( $value['value'] ) ) { |
|
2462 $conditions[] = "`$field` IS NULL"; |
|
2463 continue; |
|
2464 } |
|
2465 |
|
2466 $conditions[] = "`$field` = " . $value['format']; |
|
2467 $values[] = $value['value']; |
|
2468 } |
|
2469 |
|
2470 $fields = implode( ', ', $fields ); |
|
2471 $conditions = implode( ' AND ', $conditions ); |
|
2472 |
|
2473 $sql = "UPDATE `$table` SET $fields WHERE $conditions"; |
|
2474 |
|
2475 $this->check_current_query = false; |
|
2476 return $this->query( $this->prepare( $sql, $values ) ); |
|
2477 } |
|
2478 |
|
2479 /** |
|
2480 * Deletes a row in the table. |
|
2481 * |
|
2482 * Examples: |
|
2483 * |
|
2484 * wpdb::delete( 'table', array( 'ID' => 1 ) ) |
|
2485 * wpdb::delete( 'table', array( 'ID' => 1 ), array( '%d' ) ) |
|
2486 * |
|
2487 * @since 3.4.0 |
|
2488 * |
|
2489 * @see wpdb::prepare() |
|
2490 * @see wpdb::$field_types |
|
2491 * @see wp_set_wpdb_vars() |
|
2492 * |
|
2493 * @param string $table Table name. |
|
2494 * @param array $where A named array of WHERE clauses (in column => value pairs). |
|
2495 * Multiple clauses will be joined with ANDs. |
|
2496 * Both $where columns and $where values should be "raw". |
|
2497 * Sending a null value will create an IS NULL comparison - the corresponding |
|
2498 * format will be ignored in this case. |
|
2499 * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where. |
|
2500 * If string, that format will be used for all of the items in $where. |
|
2501 * A format is one of '%d', '%f', '%s' (integer, float, string). |
|
2502 * If omitted, all values in $data will be treated as strings unless otherwise |
|
2503 * specified in wpdb::$field_types. |
|
2504 * @return int|false The number of rows updated, or false on error. |
|
2505 */ |
|
2506 public function delete( $table, $where, $where_format = null ) { |
|
2507 if ( ! is_array( $where ) ) { |
|
2508 return false; |
|
2509 } |
|
2510 |
|
2511 $where = $this->process_fields( $table, $where, $where_format ); |
|
2512 if ( false === $where ) { |
|
2513 return false; |
|
2514 } |
|
2515 |
|
2516 $conditions = array(); |
|
2517 $values = array(); |
|
2518 foreach ( $where as $field => $value ) { |
|
2519 if ( is_null( $value['value'] ) ) { |
|
2520 $conditions[] = "`$field` IS NULL"; |
|
2521 continue; |
|
2522 } |
|
2523 |
|
2524 $conditions[] = "`$field` = " . $value['format']; |
|
2525 $values[] = $value['value']; |
|
2526 } |
|
2527 |
|
2528 $conditions = implode( ' AND ', $conditions ); |
|
2529 |
|
2530 $sql = "DELETE FROM `$table` WHERE $conditions"; |
|
2531 |
|
2532 $this->check_current_query = false; |
|
2533 return $this->query( $this->prepare( $sql, $values ) ); |
|
2534 } |
|
2535 |
|
2536 /** |
|
2537 * Processes arrays of field/value pairs and field formats. |
|
2538 * |
|
2539 * This is a helper method for wpdb's CRUD methods, which take field/value pairs |
|
2540 * for inserts, updates, and where clauses. This method first pairs each value |
|
2541 * with a format. Then it determines the charset of that field, using that |
|
2542 * to determine if any invalid text would be stripped. If text is stripped, |
|
2543 * then field processing is rejected and the query fails. |
|
2544 * |
|
2545 * @since 4.2.0 |
|
2546 * |
|
2547 * @param string $table Table name. |
|
2548 * @param array $data Field/value pair. |
|
2549 * @param mixed $format Format for each field. |
|
2550 * @return array|false An array of fields that contain paired value and formats. |
|
2551 * False for invalid values. |
|
2552 */ |
|
2553 protected function process_fields( $table, $data, $format ) { |
|
2554 $data = $this->process_field_formats( $data, $format ); |
|
2555 if ( false === $data ) { |
|
2556 return false; |
|
2557 } |
|
2558 |
|
2559 $data = $this->process_field_charsets( $data, $table ); |
|
2560 if ( false === $data ) { |
|
2561 return false; |
|
2562 } |
|
2563 |
|
2564 $data = $this->process_field_lengths( $data, $table ); |
|
2565 if ( false === $data ) { |
|
2566 return false; |
|
2567 } |
|
2568 |
|
2569 $converted_data = $this->strip_invalid_text( $data ); |
|
2570 |
|
2571 if ( $data !== $converted_data ) { |
|
2572 |
|
2573 $problem_fields = array(); |
|
2574 foreach ( $data as $field => $value ) { |
|
2575 if ( $value !== $converted_data[ $field ] ) { |
|
2576 $problem_fields[] = $field; |
|
2577 } |
|
2578 } |
|
2579 |
|
2580 wp_load_translations_early(); |
|
2581 |
|
2582 if ( 1 === count( $problem_fields ) ) { |
|
2583 $this->last_error = sprintf( |
|
2584 /* translators: %s: Database field where the error occurred. */ |
|
2585 __( 'WordPress database error: Processing the value for the following field failed: %s. The supplied value may be too long or contains invalid data.' ), |
|
2586 reset( $problem_fields ) |
|
2587 ); |
|
2588 } else { |
|
2589 $this->last_error = sprintf( |
|
2590 /* translators: %s: Database fields where the error occurred. */ |
|
2591 __( 'WordPress database error: Processing the values for the following fields failed: %s. The supplied values may be too long or contain invalid data.' ), |
|
2592 implode( ', ', $problem_fields ) |
|
2593 ); |
|
2594 } |
|
2595 |
|
2596 return false; |
|
2597 } |
|
2598 |
|
2599 return $data; |
|
2600 } |
|
2601 |
|
2602 /** |
|
2603 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods. |
|
2604 * |
|
2605 * @since 4.2.0 |
|
2606 * |
|
2607 * @param array $data Array of fields to values. |
|
2608 * @param mixed $format Formats to be mapped to the values in $data. |
|
2609 * @return array Array, keyed by field names with values being an array |
|
2610 * of 'value' and 'format' keys. |
|
2611 */ |
|
2612 protected function process_field_formats( $data, $format ) { |
|
2613 $formats = (array) $format; |
|
2614 $original_formats = $formats; |
|
2615 |
|
2616 foreach ( $data as $field => $value ) { |
|
2617 $value = array( |
|
2618 'value' => $value, |
|
2619 'format' => '%s', |
|
2620 ); |
|
2621 |
|
2622 if ( ! empty( $format ) ) { |
|
2623 $value['format'] = array_shift( $formats ); |
|
2624 if ( ! $value['format'] ) { |
|
2625 $value['format'] = reset( $original_formats ); |
|
2626 } |
|
2627 } elseif ( isset( $this->field_types[ $field ] ) ) { |
|
2628 $value['format'] = $this->field_types[ $field ]; |
|
2629 } |
|
2630 |
|
2631 $data[ $field ] = $value; |
|
2632 } |
|
2633 |
|
2634 return $data; |
|
2635 } |
|
2636 |
|
2637 /** |
|
2638 * Adds field charsets to field/value/format arrays generated by wpdb::process_field_formats(). |
|
2639 * |
|
2640 * @since 4.2.0 |
|
2641 * |
|
2642 * @param array $data As it comes from the wpdb::process_field_formats() method. |
|
2643 * @param string $table Table name. |
|
2644 * @return array|false The same array as $data with additional 'charset' keys. |
|
2645 * False on failure. |
|
2646 */ |
|
2647 protected function process_field_charsets( $data, $table ) { |
|
2648 foreach ( $data as $field => $value ) { |
|
2649 if ( '%d' === $value['format'] || '%f' === $value['format'] ) { |
|
2650 /* |
|
2651 * We can skip this field if we know it isn't a string. |
|
2652 * This checks %d/%f versus ! %s because its sprintf() could take more. |
|
2653 */ |
|
2654 $value['charset'] = false; |
|
2655 } else { |
|
2656 $value['charset'] = $this->get_col_charset( $table, $field ); |
|
2657 if ( is_wp_error( $value['charset'] ) ) { |
|
2658 return false; |
|
2659 } |
|
2660 } |
|
2661 |
|
2662 $data[ $field ] = $value; |
|
2663 } |
|
2664 |
|
2665 return $data; |
|
2666 } |
|
2667 |
|
2668 /** |
|
2669 * For string fields, records the maximum string length that field can safely save. |
|
2670 * |
|
2671 * @since 4.2.1 |
|
2672 * |
|
2673 * @param array $data As it comes from the wpdb::process_field_charsets() method. |
|
2674 * @param string $table Table name. |
|
2675 * @return array|false The same array as $data with additional 'length' keys, or false if |
|
2676 * any of the values were too long for their corresponding field. |
|
2677 */ |
|
2678 protected function process_field_lengths( $data, $table ) { |
|
2679 foreach ( $data as $field => $value ) { |
|
2680 if ( '%d' === $value['format'] || '%f' === $value['format'] ) { |
|
2681 /* |
|
2682 * We can skip this field if we know it isn't a string. |
|
2683 * This checks %d/%f versus ! %s because its sprintf() could take more. |
|
2684 */ |
|
2685 $value['length'] = false; |
|
2686 } else { |
|
2687 $value['length'] = $this->get_col_length( $table, $field ); |
|
2688 if ( is_wp_error( $value['length'] ) ) { |
|
2689 return false; |
|
2690 } |
|
2691 } |
|
2692 |
|
2693 $data[ $field ] = $value; |
|
2694 } |
|
2695 |
|
2696 return $data; |
|
2697 } |
|
2698 |
|
2699 /** |
|
2700 * Retrieves one variable from the database. |
|
2701 * |
|
2702 * Executes a SQL query and returns the value from the SQL result. |
|
2703 * If the SQL result contains more than one column and/or more than one row, |
|
2704 * the value in the column and row specified is returned. If $query is null, |
|
2705 * the value in the specified column and row from the previous SQL result is returned. |
|
2706 * |
|
2707 * @since 0.71 |
|
2708 * |
|
2709 * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query. |
|
2710 * @param int $x Optional. Column of value to return. Indexed from 0. |
|
2711 * @param int $y Optional. Row of value to return. Indexed from 0. |
|
2712 * @return string|null Database query result (as string), or null on failure. |
|
2713 */ |
|
2714 public function get_var( $query = null, $x = 0, $y = 0 ) { |
|
2715 $this->func_call = "\$db->get_var(\"$query\", $x, $y)"; |
|
2716 |
|
2717 if ( $query ) { |
|
2718 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { |
|
2719 $this->check_current_query = false; |
|
2720 } |
|
2721 |
|
2722 $this->query( $query ); |
|
2723 } |
|
2724 |
|
2725 // Extract var out of cached results based on x,y vals. |
|
2726 if ( ! empty( $this->last_result[ $y ] ) ) { |
|
2727 $values = array_values( get_object_vars( $this->last_result[ $y ] ) ); |
|
2728 } |
|
2729 |
|
2730 // If there is a value return it, else return null. |
|
2731 return ( isset( $values[ $x ] ) && '' !== $values[ $x ] ) ? $values[ $x ] : null; |
|
2732 } |
|
2733 |
|
2734 /** |
|
2735 * Retrieves one row from the database. |
|
2736 * |
|
2737 * Executes a SQL query and returns the row from the SQL result. |
|
2738 * |
|
2739 * @since 0.71 |
|
2740 * |
|
2741 * @param string|null $query SQL query. |
|
2742 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which |
|
2743 * correspond to an stdClass object, an associative array, or a numeric array, |
|
2744 * respectively. Default OBJECT. |
|
2745 * @param int $y Optional. Row to return. Indexed from 0. |
|
2746 * @return array|object|null|void Database query result in format specified by $output or null on failure. |
|
2747 */ |
|
2748 public function get_row( $query = null, $output = OBJECT, $y = 0 ) { |
|
2749 $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; |
|
2750 |
|
2751 if ( $query ) { |
|
2752 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { |
|
2753 $this->check_current_query = false; |
|
2754 } |
|
2755 |
|
2756 $this->query( $query ); |
|
2757 } else { |
|
2758 return null; |
|
2759 } |
|
2760 |
|
2761 if ( ! isset( $this->last_result[ $y ] ) ) { |
|
2762 return null; |
|
2763 } |
|
2764 |
|
2765 if ( OBJECT === $output ) { |
|
2766 return $this->last_result[ $y ] ? $this->last_result[ $y ] : null; |
|
2767 } elseif ( ARRAY_A === $output ) { |
|
2768 return $this->last_result[ $y ] ? get_object_vars( $this->last_result[ $y ] ) : null; |
|
2769 } elseif ( ARRAY_N === $output ) { |
|
2770 return $this->last_result[ $y ] ? array_values( get_object_vars( $this->last_result[ $y ] ) ) : null; |
|
2771 } elseif ( OBJECT === strtoupper( $output ) ) { |
|
2772 // Back compat for OBJECT being previously case-insensitive. |
|
2773 return $this->last_result[ $y ] ? $this->last_result[ $y ] : null; |
|
2774 } else { |
|
2775 $this->print_error( ' $db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N' ); |
|
2776 } |
|
2777 } |
|
2778 |
|
2779 /** |
|
2780 * Retrieves one column from the database. |
|
2781 * |
|
2782 * Executes a SQL query and returns the column from the SQL result. |
|
2783 * If the SQL result contains more than one column, the column specified is returned. |
|
2784 * If $query is null, the specified column from the previous SQL result is returned. |
|
2785 * |
|
2786 * @since 0.71 |
|
2787 * |
|
2788 * @param string|null $query Optional. SQL query. Defaults to previous query. |
|
2789 * @param int $x Optional. Column to return. Indexed from 0. |
|
2790 * @return array Database query result. Array indexed from 0 by SQL result row number. |
|
2791 */ |
|
2792 public function get_col( $query = null, $x = 0 ) { |
|
2793 if ( $query ) { |
|
2794 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { |
|
2795 $this->check_current_query = false; |
|
2796 } |
|
2797 |
|
2798 $this->query( $query ); |
|
2799 } |
|
2800 |
|
2801 $new_array = array(); |
|
2802 // Extract the column values. |
|
2803 if ( $this->last_result ) { |
|
2804 for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) { |
|
2805 $new_array[ $i ] = $this->get_var( null, $x, $i ); |
|
2806 } |
|
2807 } |
|
2808 return $new_array; |
|
2809 } |
|
2810 |
|
2811 /** |
|
2812 * Retrieves an entire SQL result set from the database (i.e., many rows). |
|
2813 * |
|
2814 * Executes a SQL query and returns the entire SQL result. |
|
2815 * |
|
2816 * @since 0.71 |
|
2817 * |
|
2818 * @param string $query SQL query. |
|
2819 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants. |
|
2820 * With one of the first three, return an array of rows indexed |
|
2821 * from 0 by SQL result row number. Each row is an associative array |
|
2822 * (column => value, ...), a numerically indexed array (0 => value, ...), |
|
2823 * or an object ( ->column = value ), respectively. With OBJECT_K, |
|
2824 * return an associative array of row objects keyed by the value |
|
2825 * of each row's first column's value. Duplicate keys are discarded. |
|
2826 * @return array|object|null Database query results. |
|
2827 */ |
|
2828 public function get_results( $query = null, $output = OBJECT ) { |
|
2829 $this->func_call = "\$db->get_results(\"$query\", $output)"; |
|
2830 |
|
2831 if ( $query ) { |
|
2832 if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { |
|
2833 $this->check_current_query = false; |
|
2834 } |
|
2835 |
|
2836 $this->query( $query ); |
|
2837 } else { |
|
2838 return null; |
|
2839 } |
|
2840 |
|
2841 $new_array = array(); |
|
2842 if ( OBJECT === $output ) { |
|
2843 // Return an integer-keyed array of row objects. |
|
2844 return $this->last_result; |
|
2845 } elseif ( OBJECT_K === $output ) { |
|
2846 // Return an array of row objects with keys from column 1. |
|
2847 // (Duplicates are discarded.) |
|
2848 if ( $this->last_result ) { |
|
2849 foreach ( $this->last_result as $row ) { |
|
2850 $var_by_ref = get_object_vars( $row ); |
|
2851 $key = array_shift( $var_by_ref ); |
|
2852 if ( ! isset( $new_array[ $key ] ) ) { |
|
2853 $new_array[ $key ] = $row; |
|
2854 } |
|
2855 } |
|
2856 } |
|
2857 return $new_array; |
|
2858 } elseif ( ARRAY_A === $output || ARRAY_N === $output ) { |
|
2859 // Return an integer-keyed array of... |
|
2860 if ( $this->last_result ) { |
|
2861 foreach ( (array) $this->last_result as $row ) { |
|
2862 if ( ARRAY_N === $output ) { |
|
2863 // ...integer-keyed row arrays. |
|
2864 $new_array[] = array_values( get_object_vars( $row ) ); |
|
2865 } else { |
|
2866 // ...column name-keyed row arrays. |
|
2867 $new_array[] = get_object_vars( $row ); |
|
2868 } |
|
2869 } |
|
2870 } |
|
2871 return $new_array; |
|
2872 } elseif ( strtoupper( $output ) === OBJECT ) { |
|
2873 // Back compat for OBJECT being previously case-insensitive. |
|
2874 return $this->last_result; |
|
2875 } |
|
2876 return null; |
|
2877 } |
|
2878 |
|
2879 /** |
|
2880 * Retrieves the character set for the given table. |
|
2881 * |
|
2882 * @since 4.2.0 |
|
2883 * |
|
2884 * @param string $table Table name. |
|
2885 * @return string|WP_Error Table character set, WP_Error object if it couldn't be found. |
|
2886 */ |
|
2887 protected function get_table_charset( $table ) { |
|
2888 $tablekey = strtolower( $table ); |
|
2889 |
|
2890 /** |
|
2891 * Filters the table charset value before the DB is checked. |
|
2892 * |
|
2893 * Returning a non-null value from the filter will effectively short-circuit |
|
2894 * checking the DB for the charset, returning that value instead. |
|
2895 * |
|
2896 * @since 4.2.0 |
|
2897 * |
|
2898 * @param string|WP_Error|null $charset The character set to use, WP_Error object |
|
2899 * if it couldn't be found. Default null. |
|
2900 * @param string $table The name of the table being checked. |
|
2901 */ |
|
2902 $charset = apply_filters( 'pre_get_table_charset', null, $table ); |
|
2903 if ( null !== $charset ) { |
|
2904 return $charset; |
|
2905 } |
|
2906 |
|
2907 if ( isset( $this->table_charset[ $tablekey ] ) ) { |
|
2908 return $this->table_charset[ $tablekey ]; |
|
2909 } |
|
2910 |
|
2911 $charsets = array(); |
|
2912 $columns = array(); |
|
2913 |
|
2914 $table_parts = explode( '.', $table ); |
|
2915 $table = '`' . implode( '`.`', $table_parts ) . '`'; |
|
2916 $results = $this->get_results( "SHOW FULL COLUMNS FROM $table" ); |
|
2917 if ( ! $results ) { |
|
2918 return new WP_Error( 'wpdb_get_table_charset_failure', __( 'Could not retrieve table charset.' ) ); |
|
2919 } |
|
2920 |
|
2921 foreach ( $results as $column ) { |
|
2922 $columns[ strtolower( $column->Field ) ] = $column; |
|
2923 } |
|
2924 |
|
2925 $this->col_meta[ $tablekey ] = $columns; |
|
2926 |
|
2927 foreach ( $columns as $column ) { |
|
2928 if ( ! empty( $column->Collation ) ) { |
|
2929 list( $charset ) = explode( '_', $column->Collation ); |
|
2930 |
|
2931 // If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters. |
|
2932 if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) { |
|
2933 $charset = 'utf8'; |
|
2934 } |
|
2935 |
|
2936 $charsets[ strtolower( $charset ) ] = true; |
|
2937 } |
|
2938 |
|
2939 list( $type ) = explode( '(', $column->Type ); |
|
2940 |
|
2941 // A binary/blob means the whole query gets treated like this. |
|
2942 if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ), true ) ) { |
|
2943 $this->table_charset[ $tablekey ] = 'binary'; |
|
2944 return 'binary'; |
|
2945 } |
|
2946 } |
|
2947 |
|
2948 // utf8mb3 is an alias for utf8. |
|
2949 if ( isset( $charsets['utf8mb3'] ) ) { |
|
2950 $charsets['utf8'] = true; |
|
2951 unset( $charsets['utf8mb3'] ); |
|
2952 } |
|
2953 |
|
2954 // Check if we have more than one charset in play. |
|
2955 $count = count( $charsets ); |
|
2956 if ( 1 === $count ) { |
|
2957 $charset = key( $charsets ); |
|
2958 } elseif ( 0 === $count ) { |
|
2959 // No charsets, assume this table can store whatever. |
|
2960 $charset = false; |
|
2961 } else { |
|
2962 // More than one charset. Remove latin1 if present and recalculate. |
|
2963 unset( $charsets['latin1'] ); |
|
2964 $count = count( $charsets ); |
|
2965 if ( 1 === $count ) { |
|
2966 // Only one charset (besides latin1). |
|
2967 $charset = key( $charsets ); |
|
2968 } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) { |
|
2969 // Two charsets, but they're utf8 and utf8mb4, use utf8. |
|
2970 $charset = 'utf8'; |
|
2971 } else { |
|
2972 // Two mixed character sets. ascii. |
|
2973 $charset = 'ascii'; |
|
2974 } |
|
2975 } |
|
2976 |
|
2977 $this->table_charset[ $tablekey ] = $charset; |
|
2978 return $charset; |
|
2979 } |
|
2980 |
|
2981 /** |
|
2982 * Retrieves the character set for the given column. |
|
2983 * |
|
2984 * @since 4.2.0 |
|
2985 * |
|
2986 * @param string $table Table name. |
|
2987 * @param string $column Column name. |
|
2988 * @return string|false|WP_Error Column character set as a string. False if the column has |
|
2989 * no character set. WP_Error object if there was an error. |
|
2990 */ |
|
2991 public function get_col_charset( $table, $column ) { |
|
2992 $tablekey = strtolower( $table ); |
|
2993 $columnkey = strtolower( $column ); |
|
2994 |
|
2995 /** |
|
2996 * Filters the column charset value before the DB is checked. |
|
2997 * |
|
2998 * Passing a non-null value to the filter will short-circuit |
|
2999 * checking the DB for the charset, returning that value instead. |
|
3000 * |
|
3001 * @since 4.2.0 |
|
3002 * |
|
3003 * @param string|null $charset The character set to use. Default null. |
|
3004 * @param string $table The name of the table being checked. |
|
3005 * @param string $column The name of the column being checked. |
|
3006 */ |
|
3007 $charset = apply_filters( 'pre_get_col_charset', null, $table, $column ); |
|
3008 if ( null !== $charset ) { |
|
3009 return $charset; |
|
3010 } |
|
3011 |
|
3012 // Skip this entirely if this isn't a MySQL database. |
|
3013 if ( empty( $this->is_mysql ) ) { |
|
3014 return false; |
|
3015 } |
|
3016 |
|
3017 if ( empty( $this->table_charset[ $tablekey ] ) ) { |
|
3018 // This primes column information for us. |
|
3019 $table_charset = $this->get_table_charset( $table ); |
|
3020 if ( is_wp_error( $table_charset ) ) { |
|
3021 return $table_charset; |
|
3022 } |
|
3023 } |
|
3024 |
|
3025 // If still no column information, return the table charset. |
|
3026 if ( empty( $this->col_meta[ $tablekey ] ) ) { |
|
3027 return $this->table_charset[ $tablekey ]; |
|
3028 } |
|
3029 |
|
3030 // If this column doesn't exist, return the table charset. |
|
3031 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { |
|
3032 return $this->table_charset[ $tablekey ]; |
|
3033 } |
|
3034 |
|
3035 // Return false when it's not a string column. |
|
3036 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) { |
|
3037 return false; |
|
3038 } |
|
3039 |
|
3040 list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation ); |
|
3041 return $charset; |
|
3042 } |
|
3043 |
|
3044 /** |
|
3045 * Retrieves the maximum string length allowed in a given column. |
|
3046 * |
|
3047 * The length may either be specified as a byte length or a character length. |
|
3048 * |
|
3049 * @since 4.2.1 |
|
3050 * |
|
3051 * @param string $table Table name. |
|
3052 * @param string $column Column name. |
|
3053 * @return array|false|WP_Error { |
|
3054 * Array of column length information, false if the column has no length (for |
|
3055 * example, numeric column), WP_Error object if there was an error. |
|
3056 * |
|
3057 * @type int $length The column length. |
|
3058 * @type string $type One of 'byte' or 'char'. |
|
3059 */ |
|
3060 public function get_col_length( $table, $column ) { |
|
3061 $tablekey = strtolower( $table ); |
|
3062 $columnkey = strtolower( $column ); |
|
3063 |
|
3064 // Skip this entirely if this isn't a MySQL database. |
|
3065 if ( empty( $this->is_mysql ) ) { |
|
3066 return false; |
|
3067 } |
|
3068 |
|
3069 if ( empty( $this->col_meta[ $tablekey ] ) ) { |
|
3070 // This primes column information for us. |
|
3071 $table_charset = $this->get_table_charset( $table ); |
|
3072 if ( is_wp_error( $table_charset ) ) { |
|
3073 return $table_charset; |
|
3074 } |
|
3075 } |
|
3076 |
|
3077 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { |
|
3078 return false; |
|
3079 } |
|
3080 |
|
3081 $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type ); |
|
3082 |
|
3083 $type = strtolower( $typeinfo[0] ); |
|
3084 if ( ! empty( $typeinfo[1] ) ) { |
|
3085 $length = trim( $typeinfo[1], ')' ); |
|
3086 } else { |
|
3087 $length = false; |
|
3088 } |
|
3089 |
|
3090 switch ( $type ) { |
|
3091 case 'char': |
|
3092 case 'varchar': |
|
3093 return array( |
|
3094 'type' => 'char', |
|
3095 'length' => (int) $length, |
|
3096 ); |
|
3097 |
|
3098 case 'binary': |
|
3099 case 'varbinary': |
|
3100 return array( |
|
3101 'type' => 'byte', |
|
3102 'length' => (int) $length, |
|
3103 ); |
|
3104 |
|
3105 case 'tinyblob': |
|
3106 case 'tinytext': |
|
3107 return array( |
|
3108 'type' => 'byte', |
|
3109 'length' => 255, // 2^8 - 1 |
|
3110 ); |
|
3111 |
|
3112 case 'blob': |
|
3113 case 'text': |
|
3114 return array( |
|
3115 'type' => 'byte', |
|
3116 'length' => 65535, // 2^16 - 1 |
|
3117 ); |
|
3118 |
|
3119 case 'mediumblob': |
|
3120 case 'mediumtext': |
|
3121 return array( |
|
3122 'type' => 'byte', |
|
3123 'length' => 16777215, // 2^24 - 1 |
|
3124 ); |
|
3125 |
|
3126 case 'longblob': |
|
3127 case 'longtext': |
|
3128 return array( |
|
3129 'type' => 'byte', |
|
3130 'length' => 4294967295, // 2^32 - 1 |
|
3131 ); |
|
3132 |
|
3133 default: |
|
3134 return false; |
|
3135 } |
|
3136 } |
|
3137 |
|
3138 /** |
|
3139 * Checks if a string is ASCII. |
|
3140 * |
|
3141 * The negative regex is faster for non-ASCII strings, as it allows |
|
3142 * the search to finish as soon as it encounters a non-ASCII character. |
|
3143 * |
|
3144 * @since 4.2.0 |
|
3145 * |
|
3146 * @param string $string String to check. |
|
3147 * @return bool True if ASCII, false if not. |
|
3148 */ |
|
3149 protected function check_ascii( $string ) { |
|
3150 if ( function_exists( 'mb_check_encoding' ) ) { |
|
3151 if ( mb_check_encoding( $string, 'ASCII' ) ) { |
|
3152 return true; |
|
3153 } |
|
3154 } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) { |
|
3155 return true; |
|
3156 } |
|
3157 |
|
3158 return false; |
|
3159 } |
|
3160 |
|
3161 /** |
|
3162 * Checks if the query is accessing a collation considered safe on the current version of MySQL. |
|
3163 * |
|
3164 * @since 4.2.0 |
|
3165 * |
|
3166 * @param string $query The query to check. |
|
3167 * @return bool True if the collation is safe, false if it isn't. |
|
3168 */ |
|
3169 protected function check_safe_collation( $query ) { |
|
3170 if ( $this->checking_collation ) { |
|
3171 return true; |
|
3172 } |
|
3173 |
|
3174 // We don't need to check the collation for queries that don't read data. |
|
3175 $query = ltrim( $query, "\r\n\t (" ); |
|
3176 if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) { |
|
3177 return true; |
|
3178 } |
|
3179 |
|
3180 // All-ASCII queries don't need extra checking. |
|
3181 if ( $this->check_ascii( $query ) ) { |
|
3182 return true; |
|
3183 } |
|
3184 |
|
3185 $table = $this->get_table_from_query( $query ); |
|
3186 if ( ! $table ) { |
|
3187 return false; |
|
3188 } |
|
3189 |
|
3190 $this->checking_collation = true; |
|
3191 $collation = $this->get_table_charset( $table ); |
|
3192 $this->checking_collation = false; |
|
3193 |
|
3194 // Tables with no collation, or latin1 only, don't need extra checking. |
|
3195 if ( false === $collation || 'latin1' === $collation ) { |
|
3196 return true; |
|
3197 } |
|
3198 |
|
3199 $table = strtolower( $table ); |
|
3200 if ( empty( $this->col_meta[ $table ] ) ) { |
|
3201 return false; |
|
3202 } |
|
3203 |
|
3204 // If any of the columns don't have one of these collations, it needs more sanity checking. |
|
3205 foreach ( $this->col_meta[ $table ] as $col ) { |
|
3206 if ( empty( $col->Collation ) ) { |
|
3207 continue; |
|
3208 } |
|
3209 |
|
3210 if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) { |
|
3211 return false; |
|
3212 } |
|
3213 } |
|
3214 |
|
3215 return true; |
|
3216 } |
|
3217 |
|
3218 /** |
|
3219 * Strips any invalid characters based on value/charset pairs. |
|
3220 * |
|
3221 * @since 4.2.0 |
|
3222 * |
|
3223 * @param array $data Array of value arrays. Each value array has the keys 'value' and 'charset'. |
|
3224 * An optional 'ascii' key can be set to false to avoid redundant ASCII checks. |
|
3225 * @return array|WP_Error The $data parameter, with invalid characters removed from each value. |
|
3226 * This works as a passthrough: any additional keys such as 'field' are |
|
3227 * retained in each value array. If we cannot remove invalid characters, |
|
3228 * a WP_Error object is returned. |
|
3229 */ |
|
3230 protected function strip_invalid_text( $data ) { |
|
3231 $db_check_string = false; |
|
3232 |
|
3233 foreach ( $data as &$value ) { |
|
3234 $charset = $value['charset']; |
|
3235 |
|
3236 if ( is_array( $value['length'] ) ) { |
|
3237 $length = $value['length']['length']; |
|
3238 $truncate_by_byte_length = 'byte' === $value['length']['type']; |
|
3239 } else { |
|
3240 $length = false; |
|
3241 // Since we have no length, we'll never truncate. Initialize the variable to false. |
|
3242 // True would take us through an unnecessary (for this case) codepath below. |
|
3243 $truncate_by_byte_length = false; |
|
3244 } |
|
3245 |
|
3246 // There's no charset to work with. |
|
3247 if ( false === $charset ) { |
|
3248 continue; |
|
3249 } |
|
3250 |
|
3251 // Column isn't a string. |
|
3252 if ( ! is_string( $value['value'] ) ) { |
|
3253 continue; |
|
3254 } |
|
3255 |
|
3256 $needs_validation = true; |
|
3257 if ( |
|
3258 // latin1 can store any byte sequence. |
|
3259 'latin1' === $charset |
|
3260 || |
|
3261 // ASCII is always OK. |
|
3262 ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) |
|
3263 ) { |
|
3264 $truncate_by_byte_length = true; |
|
3265 $needs_validation = false; |
|
3266 } |
|
3267 |
|
3268 if ( $truncate_by_byte_length ) { |
|
3269 mbstring_binary_safe_encoding(); |
|
3270 if ( false !== $length && strlen( $value['value'] ) > $length ) { |
|
3271 $value['value'] = substr( $value['value'], 0, $length ); |
|
3272 } |
|
3273 reset_mbstring_encoding(); |
|
3274 |
|
3275 if ( ! $needs_validation ) { |
|
3276 continue; |
|
3277 } |
|
3278 } |
|
3279 |
|
3280 // utf8 can be handled by regex, which is a bunch faster than a DB lookup. |
|
3281 if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) { |
|
3282 $regex = '/ |
|
3283 ( |
|
3284 (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx |
|
3285 | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx |
|
3286 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 |
|
3287 | [\xE1-\xEC][\x80-\xBF]{2} |
|
3288 | \xED[\x80-\x9F][\x80-\xBF] |
|
3289 | [\xEE-\xEF][\x80-\xBF]{2}'; |
|
3290 |
|
3291 if ( 'utf8mb4' === $charset ) { |
|
3292 $regex .= ' |
|
3293 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 |
|
3294 | [\xF1-\xF3][\x80-\xBF]{3} |
|
3295 | \xF4[\x80-\x8F][\x80-\xBF]{2} |
|
3296 '; |
|
3297 } |
|
3298 |
|
3299 $regex .= '){1,40} # ...one or more times |
|
3300 ) |
|
3301 | . # anything else |
|
3302 /x'; |
|
3303 $value['value'] = preg_replace( $regex, '$1', $value['value'] ); |
|
3304 |
|
3305 if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) { |
|
3306 $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' ); |
|
3307 } |
|
3308 continue; |
|
3309 } |
|
3310 |
|
3311 // We couldn't use any local conversions, send it to the DB. |
|
3312 $value['db'] = true; |
|
3313 $db_check_string = true; |
|
3314 } |
|
3315 unset( $value ); // Remove by reference. |
|
3316 |
|
3317 if ( $db_check_string ) { |
|
3318 $queries = array(); |
|
3319 foreach ( $data as $col => $value ) { |
|
3320 if ( ! empty( $value['db'] ) ) { |
|
3321 // We're going to need to truncate by characters or bytes, depending on the length value we have. |
|
3322 if ( isset( $value['length']['type'] ) && 'byte' === $value['length']['type'] ) { |
|
3323 // Using binary causes LEFT() to truncate by bytes. |
|
3324 $charset = 'binary'; |
|
3325 } else { |
|
3326 $charset = $value['charset']; |
|
3327 } |
|
3328 |
|
3329 if ( $this->charset ) { |
|
3330 $connection_charset = $this->charset; |
|
3331 } else { |
|
3332 if ( $this->use_mysqli ) { |
|
3333 $connection_charset = mysqli_character_set_name( $this->dbh ); |
|
3334 } else { |
|
3335 $connection_charset = mysql_client_encoding(); |
|
3336 } |
|
3337 } |
|
3338 |
|
3339 if ( is_array( $value['length'] ) ) { |
|
3340 $length = sprintf( '%.0f', $value['length']['length'] ); |
|
3341 $queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] ); |
|
3342 } elseif ( 'binary' !== $charset ) { |
|
3343 // If we don't have a length, there's no need to convert binary - it will always return the same result. |
|
3344 $queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] ); |
|
3345 } |
|
3346 |
|
3347 unset( $data[ $col ]['db'] ); |
|
3348 } |
|
3349 } |
|
3350 |
|
3351 $sql = array(); |
|
3352 foreach ( $queries as $column => $query ) { |
|
3353 if ( ! $query ) { |
|
3354 continue; |
|
3355 } |
|
3356 |
|
3357 $sql[] = $query . " AS x_$column"; |
|
3358 } |
|
3359 |
|
3360 $this->check_current_query = false; |
|
3361 $row = $this->get_row( 'SELECT ' . implode( ', ', $sql ), ARRAY_A ); |
|
3362 if ( ! $row ) { |
|
3363 return new WP_Error( 'wpdb_strip_invalid_text_failure', __( 'Could not strip invalid text.' ) ); |
|
3364 } |
|
3365 |
|
3366 foreach ( array_keys( $data ) as $column ) { |
|
3367 if ( isset( $row[ "x_$column" ] ) ) { |
|
3368 $data[ $column ]['value'] = $row[ "x_$column" ]; |
|
3369 } |
|
3370 } |
|
3371 } |
|
3372 |
|
3373 return $data; |
|
3374 } |
|
3375 |
|
3376 /** |
|
3377 * Strips any invalid characters from the query. |
|
3378 * |
|
3379 * @since 4.2.0 |
|
3380 * |
|
3381 * @param string $query Query to convert. |
|
3382 * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails. |
|
3383 */ |
|
3384 protected function strip_invalid_text_from_query( $query ) { |
|
3385 // We don't need to check the collation for queries that don't read data. |
|
3386 $trimmed_query = ltrim( $query, "\r\n\t (" ); |
|
3387 if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) { |
|
3388 return $query; |
|
3389 } |
|
3390 |
|
3391 $table = $this->get_table_from_query( $query ); |
|
3392 if ( $table ) { |
|
3393 $charset = $this->get_table_charset( $table ); |
|
3394 if ( is_wp_error( $charset ) ) { |
|
3395 return $charset; |
|
3396 } |
|
3397 |
|
3398 // We can't reliably strip text from tables containing binary/blob columns. |
|
3399 if ( 'binary' === $charset ) { |
|
3400 return $query; |
|
3401 } |
|
3402 } else { |
|
3403 $charset = $this->charset; |
|
3404 } |
|
3405 |
|
3406 $data = array( |
|
3407 'value' => $query, |
|
3408 'charset' => $charset, |
|
3409 'ascii' => false, |
|
3410 'length' => false, |
|
3411 ); |
|
3412 |
|
3413 $data = $this->strip_invalid_text( array( $data ) ); |
|
3414 if ( is_wp_error( $data ) ) { |
|
3415 return $data; |
|
3416 } |
|
3417 |
|
3418 return $data[0]['value']; |
|
3419 } |
|
3420 |
|
3421 /** |
|
3422 * Strips any invalid characters from the string for a given table and column. |
|
3423 * |
|
3424 * @since 4.2.0 |
|
3425 * |
|
3426 * @param string $table Table name. |
|
3427 * @param string $column Column name. |
|
3428 * @param string $value The text to check. |
|
3429 * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails. |
|
3430 */ |
|
3431 public function strip_invalid_text_for_column( $table, $column, $value ) { |
|
3432 if ( ! is_string( $value ) ) { |
|
3433 return $value; |
|
3434 } |
|
3435 |
|
3436 $charset = $this->get_col_charset( $table, $column ); |
|
3437 if ( ! $charset ) { |
|
3438 // Not a string column. |
|
3439 return $value; |
|
3440 } elseif ( is_wp_error( $charset ) ) { |
|
3441 // Bail on real errors. |
|
3442 return $charset; |
|
3443 } |
|
3444 |
|
3445 $data = array( |
|
3446 $column => array( |
|
3447 'value' => $value, |
|
3448 'charset' => $charset, |
|
3449 'length' => $this->get_col_length( $table, $column ), |
|
3450 ), |
|
3451 ); |
|
3452 |
|
3453 $data = $this->strip_invalid_text( $data ); |
|
3454 if ( is_wp_error( $data ) ) { |
|
3455 return $data; |
|
3456 } |
|
3457 |
|
3458 return $data[ $column ]['value']; |
|
3459 } |
|
3460 |
|
3461 /** |
|
3462 * Finds the first table name referenced in a query. |
|
3463 * |
|
3464 * @since 4.2.0 |
|
3465 * |
|
3466 * @param string $query The query to search. |
|
3467 * @return string|false The table name found, or false if a table couldn't be found. |
|
3468 */ |
|
3469 protected function get_table_from_query( $query ) { |
|
3470 // Remove characters that can legally trail the table name. |
|
3471 $query = rtrim( $query, ';/-#' ); |
|
3472 |
|
3473 // Allow (select...) union [...] style queries. Use the first query's table name. |
|
3474 $query = ltrim( $query, "\r\n\t (" ); |
|
3475 |
|
3476 // Strip everything between parentheses except nested selects. |
|
3477 $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query ); |
|
3478 |
|
3479 // Quickly match most common queries. |
|
3480 if ( preg_match( |
|
3481 '/^\s*(?:' |
|
3482 . 'SELECT.*?\s+FROM' |
|
3483 . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?' |
|
3484 . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?' |
|
3485 . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?' |
|
3486 . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?' |
|
3487 . ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is', |
|
3488 $query, |
|
3489 $maybe |
|
3490 ) ) { |
|
3491 return str_replace( '`', '', $maybe[1] ); |
|
3492 } |
|
3493 |
|
3494 // SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts' |
|
3495 if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) { |
|
3496 return $maybe[2]; |
|
3497 } |
|
3498 |
|
3499 /* |
|
3500 * SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%' |
|
3501 * This quoted LIKE operand seldom holds a full table name. |
|
3502 * It is usually a pattern for matching a prefix so we just |
|
3503 * strip the trailing % and unescape the _ to get 'wp_123_' |
|
3504 * which drop-ins can use for routing these SQL statements. |
|
3505 */ |
|
3506 if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) { |
|
3507 return str_replace( '\\_', '_', $maybe[2] ); |
|
3508 } |
|
3509 |
|
3510 // Big pattern for the rest of the table-related queries. |
|
3511 if ( preg_match( |
|
3512 '/^\s*(?:' |
|
3513 . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM' |
|
3514 . '|DESCRIBE|DESC|EXPLAIN|HANDLER' |
|
3515 . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?' |
|
3516 . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE' |
|
3517 . '|TRUNCATE(?:\s+TABLE)?' |
|
3518 . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?' |
|
3519 . '|ALTER(?:\s+IGNORE)?\s+TABLE' |
|
3520 . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?' |
|
3521 . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON' |
|
3522 . '|DROP\s+INDEX.*\s+ON' |
|
3523 . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE' |
|
3524 . '|(?:GRANT|REVOKE).*ON\s+TABLE' |
|
3525 . '|SHOW\s+(?:.*FROM|.*TABLE)' |
|
3526 . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', |
|
3527 $query, |
|
3528 $maybe |
|
3529 ) ) { |
|
3530 return str_replace( '`', '', $maybe[1] ); |
|
3531 } |
|
3532 |
|
3533 return false; |
|
3534 } |
|
3535 |
|
3536 /** |
|
3537 * Loads the column metadata from the last query. |
|
3538 * |
|
3539 * @since 3.5.0 |
|
3540 */ |
|
3541 protected function load_col_info() { |
|
3542 if ( $this->col_info ) { |
|
3543 return; |
|
3544 } |
|
3545 |
|
3546 if ( $this->use_mysqli ) { |
|
3547 $num_fields = mysqli_num_fields( $this->result ); |
|
3548 for ( $i = 0; $i < $num_fields; $i++ ) { |
|
3549 $this->col_info[ $i ] = mysqli_fetch_field( $this->result ); |
|
3550 } |
|
3551 } else { |
|
3552 $num_fields = mysql_num_fields( $this->result ); |
|
3553 for ( $i = 0; $i < $num_fields; $i++ ) { |
|
3554 $this->col_info[ $i ] = mysql_fetch_field( $this->result, $i ); |
|
3555 } |
|
3556 } |
|
3557 } |
|
3558 |
|
3559 /** |
|
3560 * Retrieves column metadata from the last query. |
|
3561 * |
|
3562 * @since 0.71 |
|
3563 * |
|
3564 * @param string $info_type Optional. Possible values include 'name', 'table', 'def', 'max_length', |
|
3565 * 'not_null', 'primary_key', 'multiple_key', 'unique_key', 'numeric', |
|
3566 * 'blob', 'type', 'unsigned', 'zerofill'. Default 'name'. |
|
3567 * @param int $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length. |
|
3568 * 3: if the col is numeric. 4: col's type. Default -1. |
|
3569 * @return mixed Column results. |
|
3570 */ |
|
3571 public function get_col_info( $info_type = 'name', $col_offset = -1 ) { |
|
3572 $this->load_col_info(); |
|
3573 |
|
3574 if ( $this->col_info ) { |
|
3575 if ( -1 === $col_offset ) { |
|
3576 $i = 0; |
|
3577 $new_array = array(); |
|
3578 foreach ( (array) $this->col_info as $col ) { |
|
3579 $new_array[ $i ] = $col->{$info_type}; |
|
3580 $i++; |
|
3581 } |
|
3582 return $new_array; |
|
3583 } else { |
|
3584 return $this->col_info[ $col_offset ]->{$info_type}; |
|
3585 } |
|
3586 } |
|
3587 } |
|
3588 |
|
3589 /** |
|
3590 * Starts the timer, for debugging purposes. |
|
3591 * |
|
3592 * @since 1.5.0 |
|
3593 * |
|
3594 * @return true |
|
3595 */ |
|
3596 public function timer_start() { |
|
3597 $this->time_start = microtime( true ); |
|
3598 return true; |
|
3599 } |
|
3600 |
|
3601 /** |
|
3602 * Stops the debugging timer. |
|
3603 * |
|
3604 * @since 1.5.0 |
|
3605 * |
|
3606 * @return float Total time spent on the query, in seconds. |
|
3607 */ |
|
3608 public function timer_stop() { |
|
3609 return ( microtime( true ) - $this->time_start ); |
|
3610 } |
|
3611 |
|
3612 /** |
|
3613 * Wraps errors in a nice header and footer and dies. |
|
3614 * |
|
3615 * Will not die if wpdb::$show_errors is false. |
|
3616 * |
|
3617 * @since 1.5.0 |
|
3618 * |
|
3619 * @param string $message The error message. |
|
3620 * @param string $error_code Optional. A computer-readable string to identify the error. |
|
3621 * Default '500'. |
|
3622 * @return void|false Void if the showing of errors is enabled, false if disabled. |
|
3623 */ |
|
3624 public function bail( $message, $error_code = '500' ) { |
|
3625 if ( $this->show_errors ) { |
|
3626 $error = ''; |
|
3627 |
|
3628 if ( $this->use_mysqli ) { |
|
3629 if ( $this->dbh instanceof mysqli ) { |
|
3630 $error = mysqli_error( $this->dbh ); |
|
3631 } elseif ( mysqli_connect_errno() ) { |
|
3632 $error = mysqli_connect_error(); |
|
3633 } |
|
3634 } else { |
|
3635 if ( is_resource( $this->dbh ) ) { |
|
3636 $error = mysql_error( $this->dbh ); |
|
3637 } else { |
|
3638 $error = mysql_error(); |
|
3639 } |
|
3640 } |
|
3641 |
|
3642 if ( $error ) { |
|
3643 $message = '<p><code>' . $error . "</code></p>\n" . $message; |
|
3644 } |
|
3645 |
|
3646 wp_die( $message ); |
|
3647 } else { |
|
3648 if ( class_exists( 'WP_Error', false ) ) { |
|
3649 $this->error = new WP_Error( $error_code, $message ); |
|
3650 } else { |
|
3651 $this->error = $message; |
|
3652 } |
|
3653 |
|
3654 return false; |
|
3655 } |
|
3656 } |
|
3657 |
|
3658 /** |
|
3659 * Closes the current database connection. |
|
3660 * |
|
3661 * @since 4.5.0 |
|
3662 * |
|
3663 * @return bool True if the connection was successfully closed, |
|
3664 * false if it wasn't, or if the connection doesn't exist. |
|
3665 */ |
|
3666 public function close() { |
|
3667 if ( ! $this->dbh ) { |
|
3668 return false; |
|
3669 } |
|
3670 |
|
3671 if ( $this->use_mysqli ) { |
|
3672 $closed = mysqli_close( $this->dbh ); |
|
3673 } else { |
|
3674 $closed = mysql_close( $this->dbh ); |
|
3675 } |
|
3676 |
|
3677 if ( $closed ) { |
|
3678 $this->dbh = null; |
|
3679 $this->ready = false; |
|
3680 $this->has_connected = false; |
|
3681 } |
|
3682 |
|
3683 return $closed; |
|
3684 } |
|
3685 |
|
3686 /** |
|
3687 * Determines whether MySQL database is at least the required minimum version. |
|
3688 * |
|
3689 * @since 2.5.0 |
|
3690 * |
|
3691 * @global string $wp_version The WordPress version string. |
|
3692 * @global string $required_mysql_version The required MySQL version string. |
|
3693 * @return void|WP_Error |
|
3694 */ |
|
3695 public function check_database_version() { |
|
3696 global $wp_version, $required_mysql_version; |
|
3697 // Make sure the server has the required MySQL version. |
|
3698 if ( version_compare( $this->db_version(), $required_mysql_version, '<' ) ) { |
|
3699 /* translators: 1: WordPress version number, 2: Minimum required MySQL version number. */ |
|
3700 return new WP_Error( 'database_version', sprintf( __( '<strong>Error</strong>: WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version ) ); |
|
3701 } |
|
3702 } |
|
3703 |
|
3704 /** |
|
3705 * Determines whether the database supports collation. |
|
3706 * |
|
3707 * Called when WordPress is generating the table scheme. |
|
3708 * |
|
3709 * Use `wpdb::has_cap( 'collation' )`. |
|
3710 * |
|
3711 * @since 2.5.0 |
|
3712 * @deprecated 3.5.0 Use wpdb::has_cap() |
|
3713 * |
|
3714 * @return bool True if collation is supported, false if not. |
|
3715 */ |
|
3716 public function supports_collation() { |
|
3717 _deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' ); |
|
3718 return $this->has_cap( 'collation' ); |
|
3719 } |
|
3720 |
|
3721 /** |
|
3722 * Retrieves the database character collate. |
|
3723 * |
|
3724 * @since 3.5.0 |
|
3725 * |
|
3726 * @return string The database character collate. |
|
3727 */ |
|
3728 public function get_charset_collate() { |
|
3729 $charset_collate = ''; |
|
3730 |
|
3731 if ( ! empty( $this->charset ) ) { |
|
3732 $charset_collate = "DEFAULT CHARACTER SET $this->charset"; |
|
3733 } |
|
3734 if ( ! empty( $this->collate ) ) { |
|
3735 $charset_collate .= " COLLATE $this->collate"; |
|
3736 } |
|
3737 |
|
3738 return $charset_collate; |
|
3739 } |
|
3740 |
|
3741 /** |
|
3742 * Determines if a database supports a particular feature. |
|
3743 * |
|
3744 * @since 2.7.0 |
|
3745 * @since 4.1.0 Added support for the 'utf8mb4' feature. |
|
3746 * @since 4.6.0 Added support for the 'utf8mb4_520' feature. |
|
3747 * |
|
3748 * @see wpdb::db_version() |
|
3749 * |
|
3750 * @param string $db_cap The feature to check for. Accepts 'collation', 'group_concat', |
|
3751 * 'subqueries', 'set_charset', 'utf8mb4', or 'utf8mb4_520'. |
|
3752 * @return int|false Whether the database feature is supported, false otherwise. |
|
3753 */ |
|
3754 public function has_cap( $db_cap ) { |
|
3755 $version = $this->db_version(); |
|
3756 |
|
3757 switch ( strtolower( $db_cap ) ) { |
|
3758 case 'collation': // @since 2.5.0 |
|
3759 case 'group_concat': // @since 2.7.0 |
|
3760 case 'subqueries': // @since 2.7.0 |
|
3761 return version_compare( $version, '4.1', '>=' ); |
|
3762 case 'set_charset': |
|
3763 return version_compare( $version, '5.0.7', '>=' ); |
|
3764 case 'utf8mb4': // @since 4.1.0 |
|
3765 if ( version_compare( $version, '5.5.3', '<' ) ) { |
|
3766 return false; |
|
3767 } |
|
3768 if ( $this->use_mysqli ) { |
|
3769 $client_version = mysqli_get_client_info(); |
|
3770 } else { |
|
3771 $client_version = mysql_get_client_info(); |
|
3772 } |
|
3773 |
|
3774 /* |
|
3775 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server. |
|
3776 * mysqlnd has supported utf8mb4 since 5.0.9. |
|
3777 */ |
|
3778 if ( false !== strpos( $client_version, 'mysqlnd' ) ) { |
|
3779 $client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version ); |
|
3780 return version_compare( $client_version, '5.0.9', '>=' ); |
|
3781 } else { |
|
3782 return version_compare( $client_version, '5.5.3', '>=' ); |
|
3783 } |
|
3784 case 'utf8mb4_520': // @since 4.6.0 |
|
3785 return version_compare( $version, '5.6', '>=' ); |
|
3786 } |
|
3787 |
|
3788 return false; |
|
3789 } |
|
3790 |
|
3791 /** |
|
3792 * Retrieves a comma-separated list of the names of the functions that called wpdb. |
|
3793 * |
|
3794 * @since 2.5.0 |
|
3795 * |
|
3796 * @return string Comma-separated list of the calling functions. |
|
3797 */ |
|
3798 public function get_caller() { |
|
3799 return wp_debug_backtrace_summary( __CLASS__ ); |
|
3800 } |
|
3801 |
|
3802 /** |
|
3803 * Retrieves the database server version. |
|
3804 * |
|
3805 * @since 2.7.0 |
|
3806 * |
|
3807 * @return string|null Version number on success, null on failure. |
|
3808 */ |
|
3809 public function db_version() { |
|
3810 return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() ); |
|
3811 } |
|
3812 |
|
3813 /** |
|
3814 * Retrieves full database server information. |
|
3815 * |
|
3816 * @since 5.5.0 |
|
3817 * |
|
3818 * @return string|false Server info on success, false on failure. |
|
3819 */ |
|
3820 public function db_server_info() { |
|
3821 if ( $this->use_mysqli ) { |
|
3822 $server_info = mysqli_get_server_info( $this->dbh ); |
|
3823 } else { |
|
3824 $server_info = mysql_get_server_info( $this->dbh ); |
|
3825 } |
|
3826 |
|
3827 return $server_info; |
|
3828 } |
|
3829 } |
|