: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// 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 ) {
$key = 2; // Keys 0 and 1 in $split_query contain values before the first placeholder.
$arg_identifiers = 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 ];
while ( $k <= $l && '%' === $s[ $l - $k ] ) {
$placeholder = '%' . ( $k % 2 ? '%' : '' ) . $format . $type;
// Force floats to be locale-unaware.
$placeholder = '%' . $format . $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 );
$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 );
$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;
// 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();
// 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 );
$used_placeholders[ $arg_pos ][] = $placeholder;
foreach ( $dual_use as $arg_pos ) {
$conflicts[] = implode( ' and ', $used_placeholders[ $arg_pos ] );
/* 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 )
$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();
__( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ),
* 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();
/* 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).' ),
* 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 ) {
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;
if ( ! is_scalar( $value ) && ! is_null( $value ) ) {
wp_load_translations_early();
/* translators: %s: Value type. */
__( 'Unsupported value type (%s).' ),
// Preserving old behavior, where values are escaped as strings.
$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:
* $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 );
* $sql = esc_sql( $wpdb->esc_like( $input ) );
* @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, '_%\\' );
* @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 = '' ) {
$str = mysqli_error( $this->dbh );
'query' => $this->last_query,
if ( $this->suppress_errors ) {
$caller = $this->get_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 );
$error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query );
// Are we showing errors?
if ( ! $this->show_errors ) {
wp_load_translations_early();
// If there is an error then take note of it.
__( 'WordPress database error:' ),
if ( defined( 'ERRORLOGFILE' ) ) {
error_log( $msg, 3, ERRORLOGFILE );
if ( defined( 'DIEONDBERROR' ) ) {
$str = htmlspecialchars( $str, ENT_QUOTES );
$query = htmlspecialchars( $this->last_query, ENT_QUOTES );
'<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
__( 'WordPress database error:' ),
* 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.
* @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;
* Disables showing of database errors.
* By default database errors are not shown.
* @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;
* Enables or disables suppressing of database errors.
* By default database errors are suppressed.
* @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;
* Kills cached query results.
public function flush() {
$this->last_result = array();
$this->last_query = null;
$this->rows_affected = 0;
if ( $this->result instanceof mysqli_result ) {
mysqli_free_result( $this->result );
// Confidence check before using the handle.
if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) {
// 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.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 ) {
$client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;
* Set the MySQLi error reporting off because WordPress handles its own.
* This is due to the default value change from `MYSQLI_REPORT_OFF`
* to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1.
mysqli_report( MYSQLI_REPORT_OFF );
$this->dbh = mysqli_init();
$host_data = $this->parse_db_host( $this->dbhost );
list( $host, $port, $socket, $is_ipv6 ) = $host_data;
* If using the `mysqlnd` library, the IPv6 address needs to be enclosed
* in square brackets, whereas it doesn't while using the `libmysqlclient` library.
* @see https://bugs.php.net/bug.php?id=67563
if ( $is_ipv6 && extension_loaded( 'mysqlnd' ) ) {
mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
@mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
if ( $this->dbh->connect_errno ) {
if ( ! $this->dbh && $allow_bail ) {
wp_load_translations_early();
// Load custom DB error template, if present.
if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
require_once WP_CONTENT_DIR . '/db-error.php';