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