: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
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 ) ) {
$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;
* @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;
* @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 ( 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;
return $this->base_prefix . $blog_id . '_';
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 6.1.0 `old` now includes deprecated multisite global tables only on multisite.
* @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 ) {
$tables = array_merge( $this->global_tables, $this->tables );
$tables = array_merge( $tables, $this->ms_global_tables );
$tables = $this->global_tables;
$tables = array_merge( $tables, $this->ms_global_tables );
$tables = $this->ms_global_tables;
$tables = $this->old_tables;
$tables = array_merge( $tables, $this->old_ms_global_tables );
$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;
$tables[ $table ] = $blog_prefix . $table;
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;
* 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.
* @param string $db Database name.
* @param mysqli $dbh Optional. Database connection.
* Defaults to the current database handle.
public function select( $db, $dbh = null ) {
$success = mysqli_select_db( $dbh, $db );
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>'
$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>'
$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 )
$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/' )
$this->bail( $message, 'db_select_fail' );
* Do not use, deprecated.
* Use esc_sql() or wpdb::prepare() instead.
* @deprecated 3.6.0 Use wpdb::prepare()
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().
* @see mysqli_real_escape_string()
* @param string $data String to escape.
* @return string Escaped string.
public function _real_escape( $data ) {
if ( ! is_scalar( $data ) ) {
$escaped = mysqli_real_escape_string( $this->dbh, $data );
$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.
* @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 ) {
$data[ $k ] = $this->_escape( $v );
$data[ $k ] = $this->_real_escape( $v );
$data = $this->_real_escape( $data );
* Do not use, deprecated.
* Use esc_sql() or wpdb::prepare() instead.
* @deprecated 3.6.0 Use wpdb::prepare()
* @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 ) {
$data[ $k ] = $this->escape( $v, 'recursive' );
$data[ $k ] = $this->_weak_escape( $v, 'internal' );
$data = $this->_weak_escape( $data, 'internal' );
* Escapes content by reference for insertion into the database, for security.
* @uses wpdb::_real_escape()
* @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.
* @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`.
* @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:
* - `%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.
* "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s",
* array( 'foo', 1337, '%bar' )
* "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s",
* @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
* @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 ) ) {
* 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();
/* translators: %s: wpdb::prepare() */
__( 'The query argument of %s must have a placeholder.' ),
* 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 );