File "class-wpdb.php"
Full Path: /home/ycoalition/public_html/blog/wp-includes/blocks/pattern/class-wpdb.php
File size: 48 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* WordPress database access abstraction class.
*
* Original code from {@link http://php.justinvincent.com Justin Vincent (justin@visunet.ie)}
*
* @package WordPress
* @subpackage Database
* @since 0.71
*/
/**
* @since 0.71
*/
define( 'EZSQL_VERSION', 'WP1.25' );
/**
* @since 0.71
*/
define( 'OBJECT', 'OBJECT' );
// phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase
define( 'object', 'OBJECT' ); // Back compat.
/**
* @since 2.5.0
*/
define( 'OBJECT_K', 'OBJECT_K' );
/**
* @since 0.71
*/
define( 'ARRAY_A', 'ARRAY_A' );
/**
* @since 0.71
*/
define( 'ARRAY_N', 'ARRAY_N' );
/**
* WordPress database access abstraction class.
*
* This class is used to interact with a database without needing to use raw SQL statements.
* By default, WordPress uses this class to instantiate the global $wpdb object, providing
* access to the WordPress database.
*
* It is possible to replace this class with your own by setting the $wpdb global variable
* in wp-content/db.php file to your class. The wpdb class will still be included, so you can
* extend it or simply use your own.
*
* @link https://developer.wordpress.org/reference/classes/wpdb/
*
* @since 0.71
*/
#[AllowDynamicProperties]
class wpdb {
/**
* Whether to show SQL/DB errors.
*
* Default is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY evaluate to true.
*
* @since 0.71
*
* @var bool
*/
public $show_errors = false;
/**
* Whether to suppress errors during the DB bootstrapping. Default false.
*
* @since 2.5.0
*
* @var bool
*/
public $suppress_errors = false;
/**
* The error encountered during the last query.
*
* @since 2.5.0
*
* @var string
*/
public $last_error = '';
/**
* The number of queries made.
*
* @since 1.2.0
*
* @var int
*/
public $num_queries = 0;
/**
* Count of rows returned by the last query.
*
* @since 0.71
*
* @var int
*/
public $num_rows = 0;
/**
* Count of rows affected by the last query.
*
* @since 0.71
*
* @var int
*/
public $rows_affected = 0;
/**
* The ID generated for an AUTO_INCREMENT column by the last query (usually INSERT).
*
* @since 0.71
*
* @var int
*/
public $insert_id = 0;
/**
* The last query made.
*
* @since 0.71
*
* @var string
*/
public $last_query;
/**
* Results of the last query.
*
* @since 0.71
*
* @var stdClass[]|null
*/
public $last_result;
/**
* Database query result.
*
* Possible values:
*
* - `mysqli_result` instance for successful SELECT, SHOW, DESCRIBE, or EXPLAIN queries
* - `true` for other query types that were successful
* - `null` if a query is yet to be made or if the result has since been flushed
* - `false` if the query returned an error
*
* @since 0.71
*
* @var mysqli_result|bool|null
*/
protected $result;
/**
* Cached column info, for confidence checking data before inserting.
*
* @since 4.2.0
*
* @var array
*/
protected $col_meta = array();
/**
* Calculated character sets keyed by table name.
*
* @since 4.2.0
*
* @var string[]
*/
protected $table_charset = array();
/**
* Whether text fields in the current query need to be confidence checked.
*
* @since 4.2.0
*
* @var bool
*/
protected $check_current_query = true;
/**
* Flag to ensure we don't run into recursion problems when checking the collation.
*
* @since 4.2.0
*
* @see wpdb::check_safe_collation()
* @var bool
*/
private $checking_collation = false;
/**
* Saved info on the table column.
*
* @since 0.71
*
* @var array
*/
protected $col_info;
/**
* Log of queries that were executed, for debugging purposes.
*
* @since 1.5.0
* @since 2.5.0 The third element in each query log was added to record the calling functions.
* @since 5.1.0 The fourth element in each query log was added to record the start time.
* @since 5.3.0 The fifth element in each query log was added to record custom data.
*
* @var array[] {
* Array of arrays containing information about queries that were executed.
*
* @type array ...$0 {
* Data for each query.
*
* @type string $0 The query's SQL.
* @type float $1 Total time spent on the query, in seconds.
* @type string $2 Comma-separated list of the calling functions.
* @type float $3 Unix timestamp of the time at the start of the query.
* @type array $4 Custom query data.
* }
* }
*/
public $queries;
/**
* The number of times to retry reconnecting before dying. Default 5.
*
* @since 3.9.0
*
* @see wpdb::check_connection()
* @var int
*/
protected $reconnect_retries = 5;
/**
* WordPress table prefix.
*
* You can set this to have multiple WordPress installations in a single database.
* The second reason is for possible security precautions.
*
* @since 2.5.0
*
* @var string
*/
public $prefix = '';
/**
* WordPress base table prefix.
*
* @since 3.0.0
*
* @var string
*/
public $base_prefix;
/**
* Whether the database queries are ready to start executing.
*
* @since 2.3.2
*
* @var bool
*/
public $ready = false;
/**
* Blog ID.
*
* @since 3.0.0
*
* @var int
*/
public $blogid = 0;
/**
* Site ID.
*
* @since 3.0.0
*
* @var int
*/
public $siteid = 0;
/**
* List of WordPress per-site tables.
*
* @since 2.5.0
*
* @see wpdb::tables()
* @var string[]
*/
public $tables = array(
'posts',
'comments',
'links',
'options',
'postmeta',
'terms',
'term_taxonomy',
'term_relationships',
'termmeta',
'commentmeta',
);
/**
* List of deprecated WordPress tables.
*
* 'categories', 'post2cat', and 'link2cat' were deprecated in 2.3.0, db version 5539.
*
* @since 2.9.0
*
* @see wpdb::tables()
* @var string[]
*/
public $old_tables = array( 'categories', 'post2cat', 'link2cat' );
/**
* List of WordPress global tables.
*
* @since 3.0.0
*
* @see wpdb::tables()
* @var string[]
*/
public $global_tables = array( 'users', 'usermeta' );
/**
* List of Multisite global tables.
*
* @since 3.0.0
*
* @see wpdb::tables()
* @var string[]
*/
public $ms_global_tables = array(
'blogs',
'blogmeta',
'signups',
'site',
'sitemeta',
'registration_log',
);
/**
* List of deprecated WordPress Multisite global tables.
*
* @since 6.1.0
*
* @see wpdb::tables()
* @var string[]
*/
public $old_ms_global_tables = array( 'sitecategories' );
/**
* WordPress Comments table.
*
* @since 1.5.0
*
* @var string
*/
public $comments;
/**
* WordPress Comment Metadata table.
*
* @since 2.9.0
*
* @var string
*/
public $commentmeta;
/**
* WordPress Links table.
*
* @since 1.5.0
*
* @var string
*/
public $links;
/**
* WordPress Options table.
*
* @since 1.5.0
*
* @var string
*/
public $options;
/**
* WordPress Post Metadata table.
*
* @since 1.5.0
*
* @var string
*/
public $postmeta;
/**
* WordPress Posts table.
*
* @since 1.5.0
*
* @var string
*/
public $posts;
/**
* WordPress Terms table.
*
* @since 2.3.0
*
* @var string
*/
public $terms;
/**
* WordPress Term Relationships table.
*
* @since 2.3.0
*
* @var string
*/
public $term_relationships;
/**
* WordPress Term Taxonomy table.
*
* @since 2.3.0
*
* @var string
*/
public $term_taxonomy;
/**
* WordPress Term Meta table.
*
* @since 4.4.0
*
* @var string
*/
public $termmeta;
//
// Global and Multisite tables
//
/**
* WordPress User Metadata table.
*
* @since 2.3.0
*
* @var string
*/
public $usermeta;
/**
* WordPress Users table.
*
* @since 1.5.0
*
* @var string
*/
public $users;
/**
* Multisite Blogs table.
*
* @since 3.0.0
*
* @var string
*/
public $blogs;
/**
* Multisite Blog Metadata table.
*
* @since 5.1.0
*
* @var string
*/
public $blogmeta;
/**
* Multisite Registration Log table.
*
* @since 3.0.0
*
* @var string
*/
public $registration_log;
/**
* Multisite Signups table.
*
* @since 3.0.0
*
* @var string
*/
public $signups;
/**
* Multisite Sites table.
*
* @since 3.0.0
*
* @var string
*/
public $site;
/**
* Multisite Sitewide Terms table.
*
* @since 3.0.0
*
* @var string
*/
public $sitecategories;
/**
* Multisite Site Metadata table.
*
* @since 3.0.0
*
* @var string
*/
public $sitemeta;
/**
* Format specifiers for DB columns.
*
* Columns not listed here default to %s. Initialized during WP load.
* Keys are column names, values are format types: 'ID' => '%d'.
*
* @since 2.8.0
*
* @see wpdb::prepare()
* @see wpdb::insert()
* @see wpdb::update()
* @see wpdb::delete()
* @see wp_set_wpdb_vars()
* @var array
*/
public $field_types = array();
/**
* Database table columns charset.
*
* @since 2.2.0
*
* @var string
*/
public $charset;
/**
* Database table columns collate.
*
* @since 2.2.0
*
* @var string
*/
public $collate;
/**
* Database Username.
*
* @since 2.9.0
*
* @var string
*/
protected $dbuser;
/**
* Database Password.
*
* @since 3.1.0
*
* @var string
*/
protected $dbpassword;
/**
* Database Name.
*
* @since 3.1.0
*
* @var string
*/
protected $dbname;
/**
* Database Host.
*
* @since 3.1.0
*
* @var string
*/
protected $dbhost;
/**
* Database handle.
*
* Possible values:
*
* - `mysqli` instance during normal operation
* - `null` if the connection is yet to be made or has been closed
* - `false` if the connection has failed
*
* @since 0.71
*
* @var mysqli|false|null
*/
protected $dbh;
/**
* A textual description of the last query/get_row/get_var call.
*
* @since 3.0.0
*
* @var string
*/
public $func_call;
/**
* Whether MySQL is used as the database engine.
*
* Set in wpdb::db_connect() to true, by default. This is used when checking
* against the required MySQL version for WordPress. Normally, a replacement
* database drop-in (db.php) will skip these checks, but setting this to true
* will force the checks to occur.
*
* @since 3.3.0
*
* @var bool
*/
public $is_mysql = null;
/**
* A list of incompatible SQL modes.
*
* @since 3.9.0
*
* @var string[]
*/
protected $incompatible_modes = array(
'NO_ZERO_DATE',
'ONLY_FULL_GROUP_BY',
'STRICT_TRANS_TABLES',
'STRICT_ALL_TABLES',
'TRADITIONAL',
'ANSI',
);
/**
* Backward compatibility, where wpdb::prepare() has not quoted formatted/argnum placeholders.
*
* This is often used for table/field names (before %i was supported), and sometimes string formatting, e.g.
*
* $wpdb->prepare( 'WHERE `%1$s` = "%2$s something %3$s" OR %1$s = "%4$-10s"', 'field_1', 'a', 'b', 'c' );
*
* But it's risky, e.g. forgetting to add quotes, resulting in SQL Injection vulnerabilities:
*
* $wpdb->prepare( 'WHERE (id = %1s) OR (id = %2$s)', $_GET['id'], $_GET['id'] ); // ?id=id
*
* This feature is preserved while plugin authors update their code to use safer approaches:
*
* $_GET['key'] = 'a`b';
*
* $wpdb->prepare( 'WHERE %1s = %s', $_GET['key'], $_GET['value'] ); // WHERE a`b = 'value'
* $wpdb->prepare( 'WHERE `%1$s` = "%2$s"', $_GET['key'], $_GET['value'] ); // WHERE `a`b` = "value"
*
* $wpdb->prepare( 'WHERE %i = %s', $_GET['key'], $_GET['value'] ); // WHERE `a``b` = 'value'
*
* While changing to false will be fine for queries not using formatted/argnum placeholders,
* any remaining cases are most likely going to result in SQL errors (good, in a way):
*
* $wpdb->prepare( 'WHERE %1$s = "%2$-10s"', 'my_field', 'my_value' );
* true = WHERE my_field = "my_value "
* false = WHERE 'my_field' = "'my_value '"
*
* But there may be some queries that result in an SQL Injection vulnerability:
*
* $wpdb->prepare( 'WHERE id = %1$s', $_GET['id'] ); // ?id=id
*
* So there may need to be a `_doing_it_wrong()` phase, after we know everyone can use
* identifier placeholders (%i), but before this feature is disabled or removed.
*
* @since 6.2.0
* @var bool
*/
private $allow_unsafe_unquoted_parameters = true;
/**
* Whether to use the mysqli extension over mysql. This is no longer used as the mysql
* extension is no longer supported.
*
* Default true.
*
* @since 3.9.0
* @since 6.4.0 This property was removed.
* @since 6.4.1 This property was reinstated and its default value was changed to true.
* The property is no longer used in core but may be accessed externally.
*
* @var bool
*/
private $use_mysqli = true;
/**
* Whether we've managed to successfully connect at some point.
*
* @since 3.9.0
*
* @var bool
*/
private $has_connected = false;
/**
* Time when the last query was performed.
*
* Only set when `SAVEQUERIES` is defined and truthy.
*
* @since 1.5.0
*
* @var float
*/
public $time_start = null;
/**
* The last SQL error that was encountered.
*
* @since 2.5.0
*
* @var WP_Error|string
*/
public $error = null;
/**
* Connects to the database server and selects a database.
*
* Does the actual setting up
* of the class properties and connection to the database.
*
* @since 2.0.8
*
* @link https://core.trac.wordpress.org/ticket/3354
*
* @param string $dbuser Database user.
* @param string $dbpassword Database password.
* @param string $dbname Database name.
* @param string $dbhost Database host.
*/
public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
if ( WP_DEBUG && WP_DEBUG_DISPLAY ) {
$this->show_errors();
}
$this->dbuser = $dbuser;
$this->dbpassword = $dbpassword;
$this->dbname = $dbname;
$this->dbhost = $dbhost;
// wp-config.php creation will manually connect when ready.
if ( defined( 'WP_SETUP_CONFIG' ) ) {
return;
}
$this->db_connect();
}
/**
* Makes private properties readable for backward compatibility.
*
* @since 3.5.0
*
* @param string $name The private member to get, and optionally process.
* @return mixed The private member.
*/
public function __get( $name ) {
if ( 'col_info' === $name ) {
$this->load_col_info();
}
return $this->$name;
}
/**
* Makes private properties settable for backward compatibility.
*
* @since 3.5.0
*
* @param string $name The private member to set.
* @param mixed $value The value to set.
*/
public function __set( $name, $value ) {
$protected_members = array(
'col_meta',
'table_charset',
'check_current_query',
'allow_unsafe_unquoted_parameters',
);
if ( in_array( $name, $protected_members, true ) ) {
return;
}
$this->$name = $value;
}
/**
* Makes private properties check-able for backward compatibility.
*
* @since 3.5.0
*
* @param string $name The private member to check.
* @return bool If the member is set or not.
*/
public function __isset( $name ) {
return isset( $this->$name );
}
/**
* Makes private properties un-settable for backward compatibility.
*
* @since 3.5.0
*
* @param string $name The private member to unset
*/
public function __unset( $name ) {
unset( $this->$name );
}
/**
* Sets $this->charset and $this->collate.
*
* @since 3.1.0
*/
public function init_charset() {
$charset = '';
$collate = '';
if ( function_exists( 'is_multisite' ) && is_multisite() ) {
$charset = 'utf8';
if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
$collate = DB_COLLATE;
} else {
$collate = 'utf8_general_ci';
}
} elseif ( defined( 'DB_COLLATE' ) ) {
$collate = DB_COLLATE;
}
if ( defined( 'DB_CHARSET' ) ) {
$charset = DB_CHARSET;
}
$charset_collate = $this->determine_charset( $charset, $collate );
$this->charset = $charset_collate['charset'];
$this->collate = $charset_collate['collate'];
}
/**
* Determines the best charset and collation to use given a charset and collation.
*
* For example, when able, utf8mb4 should be used instead of utf8.
*
* @since 4.6.0
*
* @param string $charset The character set to check.
* @param string $collate The collation to check.
* @return array {
* The most appropriate character set and collation to use.
*
* @type string $charset Character set.
* @type string $collate Collation.
* }
*/
public function determine_charset( $charset, $collate ) {
if ( ( ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
return compact( 'charset', 'collate' );
}
if ( 'utf8' === $charset ) {
$charset = 'utf8mb4';
}
if ( 'utf8mb4' === $charset ) {
// _general_ is outdated, so we can upgrade it to _unicode_, instead.
if ( ! $collate || 'utf8_general_ci' === $collate ) {
$collate = 'utf8mb4_unicode_ci';
} else {
$collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
}
}
// _unicode_520_ is a better collation, we should use that when it's available.
if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
$collate = 'utf8mb4_unicode_520_ci';
}
return compact( 'charset', 'collate' );
}
/**
* Sets the connection's character set.
*
* @since 3.1.0
*
* @param mysqli $dbh The connection returned by `mysqli_connect()`.
* @param string $charset Optional. The character set. Default null.
* @param string $collate Optional. The collation. Default null.
*/
public function set_charset( $dbh, $charset = null, $collate = null ) {
if ( ! isset( $charset ) ) {
$charset = $this->charset;
}
if ( ! isset( $collate ) ) {
$collate = $this->collate;
}
if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) {
$set_charset_succeeded = true;
if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
$set_charset_succeeded = mysqli_set_charset( $dbh, $charset );
}
if ( $set_charset_succeeded ) {
$query = $this->prepare( 'SET NAMES %s', $charset );
if ( ! empty( $collate ) ) {
$query .= $this->prepare( ' COLLATE %s', $collate );
}
mysqli_query( $dbh, $query );
}
}
}
/**
* Changes the current SQL mode, and ensures its WordPress compatibility.
*
* If no modes are passed, it will ensure the current MySQL server modes are compatible.
*
* @since 3.9.0
*
* @param array $modes Optional. A list of SQL modes to set. Default empty array.
*/
public function set_sql_mode( $modes = array() ) {
if ( empty( $modes ) ) {
$res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' );
if ( empty( $res ) ) {
return;
}
$modes_array = mysqli_fetch_array( $res );
if ( empty( $modes_array[0] ) ) {
return;
}
$modes_str = $modes_array[0];
if ( empty( $modes_str ) ) {
return;
}
$modes = explode( ',', $modes_str );
}
$modes = array_change_key_case( $modes, CASE_UPPER );
/**
* Filters the list of incompatible SQL modes to exclude.
*
* @since 3.9.0
*
* @param array $incompatible_modes An array of incompatible modes.
*/
$incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
foreach ( $modes as $i => $mode ) {
if ( in_array( $mode, $incompatible_modes, true ) ) {
unset( $modes[ $i ] );
}
}
$modes_str = implode( ',', $modes );
mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" );
}
/**
* Sets the table prefix for the WordPress tables.
*
* @since 2.5.0
*
* @param string $prefix Alphanumeric name for the new prefix.
* @param bool $set_table_names Optional. Whether the table names, e.g. wpdb::$posts,
* should be updated or not. Default true.
* @return string|WP_Error Old prefix or WP_Error on error.
*/
public function set_prefix( $prefix, $set_table_names = true ) {
if ( preg_match( '|[^a-z0-9_]|i', $prefix ) ) {
return new WP_Error( 'invalid_db_prefix', 'Invalid database prefix' );
}
$old_prefix = is_multisite() ? '' : $prefix;
if ( isset( $this->base_prefix ) ) {
$old_prefix = $this->base_prefix;
}
$this->base_prefix = $prefix;
if ( $set_table_names ) {
foreach ( $this->tables( 'global' ) as $table => $prefixed_table ) {
$this->$table = $prefixed_table;
}
if ( is_multisite() && empty( $this->blogid ) ) {
return $old_prefix;
}
$this->prefix = $this->get_blog_prefix();
foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
$this->$table = $prefixed_table;
}
foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
$this->$table = $prefixed_table;
}
}
return $old_prefix;
}
/**
* Sets blog ID.
*
* @since 3.0.0
*
* @param int $blog_id
* @param int $network_id Optional. Network ID. Default 0.
* @return int Previous blog ID.
*/
public function set_blog_id( $blog_id, $network_id = 0 ) {
if ( ! empty( $network_id ) ) {
$this->siteid = $network_id;
}
$old_blog_id = $this->blogid;
$this->blogid = $blog_id;
$this->prefix = $this->get_blog_prefix();
foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
$this->$table = $prefixed_table;
}
foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
$this->$table = $prefixed_table;
}
return $old_blog_id;
}
/**
* Gets blog prefix.
*
* @since 3.0.0
*
* @param int $blog_id Optional. Blog ID to retrieve the table prefix for.
* Defaults to the current blog ID.
* @return string Blog prefix.
*/
public function get_blog_prefix( $blog_id = null ) {
if ( is_multisite() ) {
if ( null === $blog_id ) {
$blog_id = $this->blogid;
}
$blog_id = (int) $blog_id;
if ( defined( 'MULTISITE' ) && ( 0 === $blog_id || 1 === $blog_id ) ) {
return $this->base_prefix;
} else {
return $this->base_prefix . $blog_id . '_';
}
} else {
return $this->base_prefix;
}
}
/**
* Returns an array of WordPress tables.
*
* Also allows for the `CUSTOM_USER_TABLE` and `CUSTOM_USER_META_TABLE` to override the WordPress users
* and usermeta tables that would otherwise be determined by the prefix.
*
* The `$scope` argument can take one of the following:
*
* - 'all' - returns 'all' and 'global' tables. No old tables are returned.
* - 'blog' - returns the blog-level tables for the queried blog.
* - 'global' - returns the global tables for the installation, returning multisite tables only on multisite.
* - 'ms_global' - returns the multisite global tables, regardless if current installation is multisite.
* - 'old' - returns tables which are deprecated.
*
* @since 3.0.0
* @since 6.1.0 `old` now includes deprecated multisite global tables only on multisite.
*
* @uses wpdb::$tables
* @uses wpdb::$old_tables
* @uses wpdb::$global_tables
* @uses wpdb::$ms_global_tables
* @uses wpdb::$old_ms_global_tables
*
* @param string $scope Optional. Possible values include 'all', 'global', 'ms_global', 'blog',
* or 'old' tables. Default 'all'.
* @param bool $prefix Optional. Whether to include table prefixes. If blog prefix is requested,
* then the custom users and usermeta tables will be mapped. Default true.
* @param int $blog_id Optional. The blog_id to prefix. Used only when prefix is requested.
* Defaults to `wpdb::$blogid`.
* @return string[] Table names. When a prefix is requested, the key is the unprefixed table name.
*/
public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) {
switch ( $scope ) {
case 'all':
$tables = array_merge( $this->global_tables, $this->tables );
if ( is_multisite() ) {
$tables = array_merge( $tables, $this->ms_global_tables );
}
break;
case 'blog':
$tables = $this->tables;
break;
case 'global':
$tables = $this->global_tables;
if ( is_multisite() ) {
$tables = array_merge( $tables, $this->ms_global_tables );
}
break;
case 'ms_global':
$tables = $this->ms_global_tables;
break;
case 'old':
$tables = $this->old_tables;
if ( is_multisite() ) {
$tables = array_merge( $tables, $this->old_ms_global_tables );
}
break;
default:
return array();
}
if ( $prefix ) {
if ( ! $blog_id ) {
$blog_id = $this->blogid;
}
$blog_prefix = $this->get_blog_prefix( $blog_id );
$base_prefix = $this->base_prefix;
$global_tables = array_merge( $this->global_tables, $this->ms_global_tables );
foreach ( $tables as $k => $table ) {
if ( in_array( $table, $global_tables, true ) ) {
$tables[ $table ] = $base_prefix . $table;
} else {
$tables[ $table ] = $blog_prefix . $table;
}
unset( $tables[ $k ] );
}
if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) ) {
$tables['users'] = CUSTOM_USER_TABLE;
}
if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) ) {
$tables['usermeta'] = CUSTOM_USER_META_TABLE;
}
}
return $tables;
}
/**
* Selects a database using the current or provided database connection.
*
* The database name will be changed based on the current database connection.
* On failure, the execution will bail and display a DB error.
*
* @since 0.71
*
* @param string $db Database name.
* @param mysqli $dbh Optional. Database connection.
* Defaults to the current database handle.
*/
public function select( $db, $dbh = null ) {
if ( is_null( $dbh ) ) {
$dbh = $this->dbh;
}
$success = mysqli_select_db( $dbh, $db );
if ( ! $success ) {
$this->ready = false;
if ( ! did_action( 'template_redirect' ) ) {
wp_load_translations_early();
$message = '<h1>' . __( 'Cannot select database' ) . "</h1>\n";
$message .= '<p>' . sprintf(
/* translators: %s: Database name. */
__( 'The database server could be connected to (which means your username and password is okay) but the %s database could not be selected.' ),
'<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
) . "</p>\n";
$message .= "<ul>\n";
$message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n";
$message .= '<li>' . sprintf(
/* translators: 1: Database user, 2: Database name. */
__( 'Does the user %1$s have permission to use the %2$s database?' ),
'<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '</code>',
'<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
) . "</li>\n";
$message .= '<li>' . sprintf(
/* translators: %s: Database name. */
__( '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?' ),
htmlspecialchars( $db, ENT_QUOTES )
) . "</li>\n";
$message .= "</ul>\n";
$message .= '<p>' . sprintf(
/* translators: %s: Support forums URL. */
__( '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>.' ),
__( 'https://wordpress.org/support/forums/' )
) . "</p>\n";
$this->bail( $message, 'db_select_fail' );
}
}
}
/**
* Do not use, deprecated.
*
* Use esc_sql() or wpdb::prepare() instead.
*
* @since 2.8.0
* @deprecated 3.6.0 Use wpdb::prepare()
* @see wpdb::prepare()
* @see esc_sql()
*
* @param string $data
* @return string
*/
public function _weak_escape( $data ) {
if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
_deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
}
return addslashes( $data );
}
/**
* Real escape using mysqli_real_escape_string().
*
* @since 2.8.0
*
* @see mysqli_real_escape_string()
*
* @param string $data String to escape.
* @return string Escaped string.
*/
public function _real_escape( $data ) {
if ( ! is_scalar( $data ) ) {
return '';
}
if ( $this->dbh ) {
$escaped = mysqli_real_escape_string( $this->dbh, $data );
} else {
$class = get_class( $this );
wp_load_translations_early();
/* translators: %s: Database access abstraction class, usually wpdb or a class extending wpdb. */
_doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' );
$escaped = addslashes( $data );
}
return $this->add_placeholder_escape( $escaped );
}
/**
* Escapes data. Works on arrays.
*
* @since 2.8.0
*
* @uses wpdb::_real_escape()
*
* @param string|array $data Data to escape.
* @return string|array Escaped data, in the same type as supplied.
*/
public function _escape( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $k => $v ) {
if ( is_array( $v ) ) {
$data[ $k ] = $this->_escape( $v );
} else {
$data[ $k ] = $this->_real_escape( $v );
}
}
} else {
$data = $this->_real_escape( $data );
}
return $data;
}
/**
* Do not use, deprecated.
*
* Use esc_sql() or wpdb::prepare() instead.
*
* @since 0.71
* @deprecated 3.6.0 Use wpdb::prepare()
* @see wpdb::prepare()
* @see esc_sql()
*
* @param string|array $data Data to escape.
* @return string|array Escaped data, in the same type as supplied.
*/
public function escape( $data ) {
if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
_deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
}
if ( is_array( $data ) ) {
foreach ( $data as $k => $v ) {
if ( is_array( $v ) ) {
$data[ $k ] = $this->escape( $v, 'recursive' );
} else {
$data[ $k ] = $this->_weak_escape( $v, 'internal' );
}
}
} else {
$data = $this->_weak_escape( $data, 'internal' );
}
return $data;
}
/**
* Escapes content by reference for insertion into the database, for security.
*
* @uses wpdb::_real_escape()
*
* @since 2.3.0
*
* @param string $data String to escape.
*/
public function escape_by_ref( &$data ) {
if ( ! is_float( $data ) ) {
$data = $this->_real_escape( $data );
}
}
/**
* Quotes an identifier for a MySQL database, e.g. table/field names.
*
* @since 6.2.0
*
* @param string $identifier Identifier to escape.
* @return string Escaped identifier.
*/
public function quote_identifier( $identifier ) {
return '`' . $this->_escape_identifier_value( $identifier ) . '`';
}
/**
* Escapes an identifier value without adding the surrounding quotes.
*
* - Permitted characters in quoted identifiers include the full Unicode
* Basic Multilingual Plane (BMP), except U+0000.
* - To quote the identifier itself, you need to double the character, e.g. `a``b`.
*
* @since 6.2.0
*
* @link https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
*
* @param string $identifier Identifier to escape.
* @return string Escaped identifier.
*/
private function _escape_identifier_value( $identifier ) {
return str_replace( '`', '``', $identifier );
}
/**
* Prepares a SQL query for safe execution.
*
* Uses `sprintf()`-like syntax. The following placeholders can be used in the query string:
*
* - `%d` (integer)
* - `%f` (float)
* - `%s` (string)
* - `%i` (identifier, e.g. table/field names)
*
* All placeholders MUST be left unquoted in the query string. A corresponding argument
* MUST be passed for each placeholder.
*
* Note: There is one exception to the above: for compatibility with old behavior,
* numbered or formatted string placeholders (eg, `%1$s`, `%5s`) will not have quotes
* added by this function, so should be passed with appropriate quotes around them.
*
* Literal percentage signs (`%`) in the query string must be written as `%%`. Percentage wildcards
* (for example, to use in LIKE syntax) must be passed via a substitution argument containing
* the complete LIKE string, these cannot be inserted directly in the query string.
* Also see wpdb::esc_like().
*
* Arguments may be passed as individual arguments to the method, or as a single array
* containing all arguments. A combination of the two is not supported.
*
* Examples:
*
* $wpdb->prepare(
* "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s",
* array( 'foo', 1337, '%bar' )
* );
*
* $wpdb->prepare(
* "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s",
* 'foo'
* );
*
* @since 2.3.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by updating the function signature. The second parameter was changed
* from `$args` to `...$args`.
* @since 6.2.0 Added `%i` for identifiers, e.g. table or field names.
* Check support via `wpdb::has_cap( 'identifier_placeholders' )`.
* This preserves compatibility with `sprintf()`, as the C version uses
* `%d` and `$i` as a signed integer, whereas PHP only supports `%d`.
*
* @link https://www.php.net/sprintf Description of syntax.
*
* @param string $query Query statement with `sprintf()`-like placeholders.
* @param array|mixed $args The array of variables to substitute into the query's placeholders
* if being called with an array of arguments, or the first variable
* to substitute into the query's placeholders if being called with
* individual arguments.
* @param mixed ...$args Further variables to substitute into the query's placeholders
* if being called with individual arguments.
* @return string|void Sanitized query string, if there is a query to prepare.
*/
public function prepare( $query, ...$args ) {
if ( is_null( $query ) ) {
return;
}
/*
* This is not meant to be foolproof -- but it will catch obviously incorrect usage.
*
* Note: str_contains() is not used here, as this file can be included
* directly outside of WordPress core, e.g. by HyperDB, in which case
* the polyfills from wp-includes/compat.php are not loaded.
*/
if ( false === strpos( $query, '%' ) ) {
wp_load_translations_early();
_doing_it_wrong(
'wpdb::prepare',
sprintf(
/* translators: %s: wpdb::prepare() */
__( 'The query argument of %s must have a placeholder.' ),
'wpdb::prepare()'
),
'3.9.0'
);
}
/*
* Specify the formatting allowed in a placeholder. The following are allowed:
*
* - Sign specifier, e.g. $+d
* - Numbered placeholders, e.g. %1$s
* - Padding specifier, including custom padding characters, e.g. %05s, %'#5s
* - Alignment specifier, e.g. %05-s
* - Precision specifier, e.g. %.2f
*/
$allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?';
/*
* If a %s placeholder already has quotes around it, removing the existing quotes
* and re-inserting them ensures the quotes are consistent.
*
* For backward compatibility, this is only applied to %s, and not to placeholders like %1$s,
* which are frequently used in the middle of longer strings, or as table name placeholders.
*/
$query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes.
$query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes.
// Escape any unescaped percents (i.e. anything unrecognised).
$query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdfFi]))/", '%%\\1', $query );
// Extract placeholders from the query.
$split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE );
$split_query_count = count( $split_query );
/*
* Split always returns with 1 value before the first placeholder (even with $query = "%s"),
* then 3 additional values per placeholder.
*/
$placeholder_count = ( ( $split_query_count - 1 ) / 3 );
// If args were passed as an array, as in vsprintf(), move them up.
$passed_as_array = ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) );
if ( $passed_as_array ) {
$args = $args[0];
}
$new_query = '';
$key = 2; // Keys 0 and 1 in $split_query contain values before the first placeholder.
$arg_id = 0;
$arg_identifiers = array();
$arg_strings = array();
while ( $key < $split_query_count ) {
$placeholder = $split_query[ $key ];
$format = substr( $placeholder, 1, -1 );
$type = substr( $placeholder, -1 );
if ( 'f' === $type && true === $this->allow_unsafe_unquoted_parameters
/*
* Note: str_ends_with() is not used here, as this file can be included
* directly outside of WordPress core, e.g. by HyperDB, in which case
* the polyfills from wp-includes/compat.php are not loaded.
*/
&& '%' === substr( $split_query[ $key - 1 ], -1, 1 )
) {
/*
* Before WP 6.2 the "force floats to be locale-unaware" RegEx didn't
* convert "%%%f" to "%%%F" (note the uppercase F).
* This was because it didn't check to see if the leading "%" was escaped.
* And because the "Escape any unescaped percents" RegEx used "[sdF]" in its
* negative lookahead assertion, when there was an odd number of "%", it added
* an extra "%", to give the fully escaped "%%%%f" (not a placeholder).
*/
$s = $split_query[ $key - 2 ] . $split_query[ $key - 1 ];
$k = 1;
$l = strlen( $s );
while ( $k <= $l && '%' === $s[ $l - $k ] ) {
++$k;
}
$placeholder = '%' . ( $k % 2 ? '%' : '' ) . $format . $type;
--$placeholder_count;
} else {
// Force floats to be locale-unaware.
if ( 'f' === $type ) {
$type = 'F';
$placeholder = '%' . $format . $type;
}
if ( 'i' === $type ) {
$placeholder = '`%' . $format . 's`';
// Using a simple strpos() due to previous checking (e.g. $allowed_format).
$argnum_pos = strpos( $format, '$' );
if ( false !== $argnum_pos ) {
// sprintf() argnum starts at 1, $arg_id from 0.
$arg_identifiers[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
} else {
$arg_identifiers[] = $arg_id;
}
} elseif ( 'd' !== $type && 'F' !== $type ) {
/*
* i.e. ( 's' === $type ), where 'd' and 'F' keeps $placeholder unchanged,
* and we ensure string escaping is used as a safe default (e.g. even if 'x').
*/
$argnum_pos = strpos( $format, '$' );
if ( false !== $argnum_pos ) {
$arg_strings[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
} else {
$arg_strings[] = $arg_id;
}
/*
* Unquoted strings for backward compatibility (dangerous).
* First, "numbered or formatted string placeholders (eg, %1$s, %5s)".
* Second, if "%s" has a "%" before it, even if it's unrelated (e.g. "LIKE '%%%s%%'").
*/
if ( true !== $this->allow_unsafe_unquoted_parameters
/*
* Note: str_ends_with() is not used here, as this file can be included
* directly outside of WordPress core, e.g. by HyperDB, in which case
* the polyfills from wp-includes/compat.php are not loaded.
*/
|| ( '' === $format && '%' !== substr( $split_query[ $key - 1 ], -1, 1 ) )
) {
$placeholder = "'%" . $format . "s'";
}
}
}
// Glue (-2), any leading characters (-1), then the new $placeholder.
$new_query .= $split_query[ $key - 2 ] . $split_query[ $key - 1 ] . $placeholder;
$key += 3;
++$arg_id;
}
// Replace $query; and add remaining $query characters, or index 0 if there were no placeholders.
$query = $new_query . $split_query[ $key - 2 ];
$dual_use = array_intersect( $arg_identifiers, $arg_strings );
if ( count( $dual_use ) > 0 ) {
wp_load_translations_early();
$used_placeholders = array();
$key = 2;
$arg_id = 0;
// Parse again (only used when there is an error).
while ( $key < $split_query_count ) {
$placeholder = $split_query[ $key ];
$format = substr( $placeholder, 1, -1 );
$argnum_pos = strpos( $format, '$' );
if ( false !== $argnum_pos ) {
$arg_pos = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
} else {
$arg_pos = $arg_id;
}
$used_placeholders[ $arg_pos ][] = $placeholder;
$key += 3;
++$arg_id;
}
$conflicts = array();
foreach ( $dual_use as $arg_pos ) {
$conflicts[] = implode( ' and ', $used_placeholders[ $arg_pos ] );
}
_doing_it_wrong(
'wpdb::prepare',
sprintf(
/* translators: %s: A list of placeholders found to be a problem. */
__( 'Arguments cannot be prepared as both an Identifier and Value. Found the following conflicts: %s' ),
implode( ', ', $conflicts )
),
'6.2.0'
);
return;
}
$args_count = count( $args );
if ( $args_count !== $placeholder_count ) {
if ( 1 === $placeholder_count && $passed_as_array ) {
/*
* If the passed query only expected one argument,
* but the wrong number of arguments was sent as an array, bail.
*/
wp_load_translations_early();
_doing_it_wrong(
'wpdb::prepare',
__( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ),
'4.9.0'
);
return;
} else {
/*
* If we don't have the right number of placeholders,
* but they were passed as individual arguments,
* or we were expecting multiple arguments in an array, throw a warning.
*/
wp_load_translations_early();
_doing_it_wrong(
'wpdb::prepare',
sprintf(
/* translators: 1: Number of placeholders, 2: Number of arguments passed. */
__( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ),
$placeholder_count,
$args_count
),
'4.8.3'
);
/*
* If we don't have enough arguments to match the placeholders,
* return an empty string to avoid a fatal error on PHP 8.
*/
if ( $args_count < $placeholder_count ) {
$max_numbered_placeholder = 0;
for ( $i = 2, $l = $split_query_count; $i < $l; $i += 3 ) {
// Assume a leading number is for a numbered placeholder, e.g. '%3$s'.
$argnum = (int) substr( $split_query[ $i ], 1 );
if ( $max_numbered_placeholder < $argnum ) {
$max_numbered_placeholder = $argnum;
}
}
if ( ! $max_numbered_placeholder || $args_count < $max_numbered_placeholder ) {
return '';
}
}
}
}
$args_escaped = array();
foreach ( $args as $i => $value ) {
if ( in_array( $i, $arg_identifiers, true ) ) {
$args_escaped[] = $this->_escape_identifier_value( $value );
} elseif ( is_int( $value ) || is_float( $value ) ) {
$args_escaped[] = $value;
} else {
if ( ! is_scalar( $value ) && ! is_null( $value ) ) {
wp_load_translations_early();
_doing_it_wrong(
'wpdb::prepare',
sprintf(
/* translators: %s: Value type. */
__( 'Unsupported value type (%s).' ),
gettype( $value )
),
'4.8.2'
);
// Preserving old behavior, where values are escaped as strings.
$value = '';
}
$args_escaped[] = $this->_real_escape( $value );
}
}
$query = vsprintf( $query, $args_escaped );
return $this->add_placeholder_escape( $query );
}
/**
* First half of escaping for `LIKE` special characters `%` and `_` before preparing for SQL.
*
* Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
*
* Example Prepared Statement:
*
* $wild = '%';
* $find = 'only 43% of planets';
* $like = $wild . $wpdb->esc_like( $find ) . $wild;
* $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
*
* Example Escape Chain:
*
* $sql = esc_sql( $wpdb->esc_like( $input ) );
*
* @since 4.0.0
*
* @param string $text The raw text to be escaped. The input typed by the user
* should have no extra or deleted slashes.
* @return string Text in the form of a LIKE phrase. The output is not SQL safe.
* Call wpdb::prepare() or wpdb::_real_escape() next.
*/
public function esc_like( $text ) {
return addcslashes( $text, '_%\\' );
}
/**
* Prints SQL/DB error.
*
* @since 0.71
*
* @global array $EZSQL_ERROR Stores error information of query and error string.
*
* @param string $str The error to display.
* @return void|false Void if the showing of errors is enabled, false if disabled.
*/
public function print_error( $str = '' ) {
global $EZSQL_ERROR;
if ( ! $str ) {
$str = mysqli_error( $this->dbh );
}
$EZSQL_ERROR[] = array(
'query' => $this->last_query,
'error_str' => $str,
);
if ( $this->suppress_errors ) {
return false;
}
$caller = $this->get_caller();
if ( $caller ) {
// Not translated, as this will only appear in the error log.
$error_str = sprintf( 'WordPress database error %1$s for query %2$s made by %3$s', $str, $this->last_query, $caller );
} else {
$error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query );
}
error_log( $error_str );
// Are we showing errors?
if ( ! $this->show_errors ) {
return false;
}
wp_load_translations_early();
// If there is an error then take note of it.
if ( is_multisite() ) {
$msg = sprintf(
"%s [%s]\n%s\n",
__( 'WordPress database error:' ),
$str,
$this->last_query
);
if ( defined( 'ERRORLOGFILE' ) ) {
error_log( $msg, 3, ERRORLOGFILE );
}
if ( defined( 'DIEONDBERROR' ) ) {
wp_die( $msg );
}
} else {
$str = htmlspecialchars( $str, ENT_QUOTES );
$query = htmlspecialchars( $this->last_query, ENT_QUOTES );
printf(
'<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
__( 'WordPress database error:' ),
$str,
$query
);
}
}
/**
* Enables showing of database errors.
*
* This function should be used only to enable showing of errors.
* wpdb::hide_errors() should be used instead for hiding errors.
*
* @since 0.71
*
* @see wpdb::hide_errors()
*
* @param bool $show Optional. Whether to show errors. Default true.
* @return bool Whether showing of errors was previously active.
*/
public function show_errors( $show = true ) {
$errors = $this->show_errors;
$this->show_errors = $show;
return $errors;
}
/**
* Disables showing of database errors.
*
* By default database errors are not shown.
*
* @since 0.71
*
* @see wpdb::show_errors()
*
* @return bool Whether showing of errors was previously active.
*/
public function hide_errors() {
$show = $this->show_errors;
$this->show_errors = false;
return $show;
}
/**
* Enables or disables suppressing of database errors.
*
* By default database errors are suppressed.
*
* @since 2.5.0
*
* @see wpdb::hide_errors()
*
* @param bool $suppress Optional. Whether to suppress errors. Default true.
* @return bool Whether suppressing of errors was previously active.
*/
public function suppress_errors( $suppress = true ) {
$errors = $this->suppress_errors;
$this->suppress_errors = (bool) $suppress;
return $errors;
}
/**
* Kills cached query results.
*
* @since 0.71
*/
public function flush() {
$this->last_result = array();
$this->col_info = null;
$this->last_query = null;
$this->rows_affected = 0;
$this->num_rows = 0;
$this->last_error = '';
if ( $this->result instanceof mysqli_result ) {
mysqli_free_result( $this->result );
$this->result = null;
// Confidence check before using the handle.
if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) {
return;
}
// Clear out any results from a multi-query.
while ( mysqli_more_results( $this->dbh ) ) {
mysqli_next_result( $this->dbh );
}
}
}
/**
* Connects to and selects database.
*
* If `$allow_bail` is false, the lack of database connection will need to be handled manually.
*
* @since 3.0.0
* @since 3.9.0 $allow_bail parameter added.
*
* @param bool $allow_bail Optional. Allows the function to bail. Default true.
* @return bool True with a successful connection, false on failure.
*/
public function db_connect( $allow_bail = true ) {
$this->is_mysql = true;
$client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;
/*
* Set the MySQLi error reporting of