: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n";
$message .= '<p>' . sprintf(
/* translators: 1: wp-config.php, 2: Database host. */
__( 'This either means that the username and password information in your %1$s file is incorrect or that contact with the database server at %2$s could not be established. This could mean your host’s database server is down.' ),
'<code>wp-config.php</code>',
'<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
$message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n";
$message .= '<li>' . __( 'Are you sure you have typed the correct hostname?' ) . "</li>\n";
$message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n";
$message .= '<p>' . sprintf(
/* translators: %s: Support forums URL. */
__( '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>.' ),
__( 'https://wordpress.org/support/forums/' )
$this->bail( $message, 'db_connect_fail' );
} elseif ( $this->dbh ) {
if ( ! $this->has_connected ) {
$this->has_connected = true;
$this->set_charset( $this->dbh );
$this->select( $this->dbname, $this->dbh );
* Parses the DB_HOST setting to interpret it for mysqli_real_connect().
* mysqli_real_connect() doesn't support the host param including a port or socket
* like mysql_connect() does. This duplicates how mysql_connect() detects a port
* @param string $host The DB_HOST setting to parse.
* Array containing the host, the port, the socket and
* whether it is an IPv6 address, in that order.
* False if the host couldn't be parsed.
* @type string $0 Host name.
* @type string|null $1 Port.
* @type string|null $2 Socket.
* @type bool $3 Whether it is an IPv6 address.
public function parse_db_host( $host ) {
// First peel off the socket parameter from the right, if it exists.
$socket_pos = strpos( $host, ':/' );
if ( false !== $socket_pos ) {
$socket = substr( $host, $socket_pos + 1 );
$host = substr( $host, 0, $socket_pos );
* We need to check for an IPv6 address first.
* An IPv6 address will always contain at least two colons.
if ( substr_count( $host, ':' ) > 1 ) {
$pattern = '#^(?:\[)?(?P<host>[0-9a-fA-F:]+)(?:\]:(?P<port>[\d]+))?#';
// We seem to be dealing with an IPv4 address.
$pattern = '#^(?P<host>[^:/]*)(?::(?P<port>[\d]+))?#';
$result = preg_match( $pattern, $host, $matches );
// Couldn't parse the address, bail.
$host = ! empty( $matches['host'] ) ? $matches['host'] : '';
// MySQLi port cannot be a string; must be null or an integer.
$port = ! empty( $matches['port'] ) ? absint( $matches['port'] ) : null;
return array( $host, $port, $socket, $is_ipv6 );
* Checks that the connection to the database is still up. If not, try to reconnect.
* If this function is unable to reconnect, it will forcibly die, or if called
* after the {@see 'template_redirect'} hook has been fired, return false instead.
* If `$allow_bail` is false, the lack of database connection will need to be handled manually.
* @param bool $allow_bail Optional. Allows the function to bail. Default true.
* @return bool|void True if the connection is up.
public function check_connection( $allow_bail = true ) {
if ( ! empty( $this->dbh ) && mysqli_ping( $this->dbh ) ) {
$error_reporting = false;
// Disable warnings, as we don't want to see a multitude of "unable to connect" messages.
$error_reporting = error_reporting();
error_reporting( $error_reporting & ~E_WARNING );
for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) {
* On the last try, re-enable warnings. We want to see a single instance
* of the "unable to connect" message on the bail() screen, if it appears.
if ( $this->reconnect_retries === $tries && WP_DEBUG ) {
error_reporting( $error_reporting );
if ( $this->db_connect( false ) ) {
if ( $error_reporting ) {
error_reporting( $error_reporting );
* If template_redirect has already happened, it's too late for wp_die()/dead_db().
* Let's just return and hope for the best.
if ( did_action( 'template_redirect' ) ) {
wp_load_translations_early();
$message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n";
$message .= '<p>' . sprintf(
/* translators: %s: Database host. */
__( 'This means that the contact with the database server at %s was lost. This could mean your host’s database server is down.' ),
'<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
$message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n";
$message .= '<li>' . __( 'Are you sure the database server is not under particularly heavy load?' ) . "</li>\n";
$message .= '<p>' . sprintf(
/* translators: %s: Support forums URL. */
__( '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>.' ),
__( 'https://wordpress.org/support/forums/' )
// We weren't able to reconnect, so we better bail.
$this->bail( $message, 'db_connect_fail' );
* Call dead_db() if bail didn't die, because this database is no more.
* It has ceased to be (at least temporarily).
* Performs a database query, using current database connection.
* More information can be found on the documentation page.
* @link https://developer.wordpress.org/reference/classes/wpdb/
* @param string $query Database query.
* @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
* affected/selected for all other queries. Boolean false on error.
public function query( $query ) {
$this->check_current_query = true;
* Filters the database query.
* Some queries are made before the plugins have been loaded,
* and thus cannot be filtered with this method.
* @param string $query Database query.
$query = apply_filters( 'query', $query );
// Log how the function was called.
$this->func_call = "\$db->query(\"$query\")";
// If we're writing to the database, make sure the query will write safely.
if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
$stripped_query = $this->strip_invalid_text_from_query( $query );
* strip_invalid_text_from_query() can perform queries, so we need
* to flush again, just to make sure everything is clear.
if ( $stripped_query !== $query ) {
$this->last_query = $query;
wp_load_translations_early();
$this->last_error = __( 'WordPress database error: Could not perform query because it contains invalid data.' );
$this->check_current_query = true;
// Keep track of the last query for debug.
$this->last_query = $query;
$this->_do_query( $query );
// Database server has gone away, try to reconnect.
if ( $this->dbh instanceof mysqli ) {
$mysql_errno = mysqli_errno( $this->dbh );
* $dbh is defined, but isn't a real connection.
* Something has gone horribly wrong, let's try a reconnect.
if ( empty( $this->dbh ) || 2006 === $mysql_errno ) {
if ( $this->check_connection() ) {
$this->_do_query( $query );
// If there is an error then take note of it.
if ( $this->dbh instanceof mysqli ) {
$this->last_error = mysqli_error( $this->dbh );
$this->last_error = __( 'Unable to retrieve the error message from MySQL' );
if ( $this->last_error ) {
// Clear insert_id on a subsequent failed insert.
if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
$return_val = $this->result;
} elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
$this->rows_affected = mysqli_affected_rows( $this->dbh );
// Take note of the insert_id.
if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
$this->insert_id = mysqli_insert_id( $this->dbh );
// Return number of rows affected.
$return_val = $this->rows_affected;
if ( $this->result instanceof mysqli_result ) {
while ( $row = mysqli_fetch_object( $this->result ) ) {
$this->last_result[ $num_rows ] = $row;
// Log and return the number of rows selected.
$this->num_rows = $num_rows;
* Internal function to perform the mysqli_query() call.
* @param string $query The query to run.
private function _do_query( $query ) {
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
if ( ! empty( $this->dbh ) ) {
$this->result = mysqli_query( $this->dbh, $query );
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
* @param string $query The query's SQL.
* @param float $query_time Total time spent on the query, in seconds.
* @param string $query_callstack Comma-separated list of the calling functions.
* @param float $query_start Unix timestamp of the time at the start of the query.
* @param array $query_data Custom query data.
public function log_query( $query, $query_time, $query_callstack, $query_start, $query_data ) {
* Filters the custom data to log alongside a query.
* Caution should be used when modifying any of this data, it is recommended that any additional
* information you need to store about a query be added as a new associative array element.
* @param array $query_data Custom query data.
* @param string $query The query's SQL.
* @param float $query_time Total time spent on the query, in seconds.
* @param string $query_callstack Comma-separated list of the calling functions.
* @param float $query_start Unix timestamp of the time at the start of the query.
$query_data = apply_filters( 'log_query_custom_data', $query_data, $query, $query_time, $query_callstack, $query_start );
$this->queries[] = array(
* Generates and returns a placeholder escape string for use in queries returned by ::prepare().
* @return string String to escape placeholders.
public function placeholder_escape() {
// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
// Old WP installs may not have AUTH_SALT defined.
$salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand();
$placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}';
* Add the filter to remove the placeholder escaper. Uses priority 0, so that anything
* else attached to this filter will receive the query with the placeholder string removed.
if ( false === has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) {
add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 );
* Adds a placeholder escape string, to escape anything that resembles a printf() placeholder.
* @param string $query The query to escape.
* @return string The query with the placeholder escape string inserted where necessary.
public function add_placeholder_escape( $query ) {
* To prevent returning anything that even vaguely resembles a placeholder,
* we clobber every % we can find.
return str_replace( '%', $this->placeholder_escape(), $query );
* Removes the placeholder escape strings from a query.
* @param string $query The query from which the placeholder will be removed.
* @return string The query with the placeholder removed.
public function remove_placeholder_escape( $query ) {
return str_replace( $this->placeholder_escape(), '%', $query );
* Inserts a row into the table.
* @see wpdb::$field_types
* @see wp_set_wpdb_vars()
* @param string $table Table name.
* @param array $data Data to insert (in column => value pairs).
* Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
* Sending a null value will cause the column to be set to NULL - the corresponding
* format is ignored in this case.
* @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
* If string, that format will be used for all of the values in `$data`.
* A format is one of '%d', '%f', '%s' (integer, float, string).
* If omitted, all values in `$data` will be treated as strings unless otherwise
* specified in wpdb::$field_types. Default null.
* @return int|false The number of rows inserted, or false on error.
public function insert( $table, $data, $format = null ) {
return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );