+ public function print_error( $str = '' ) {
+ global $EZSQL_ERROR;
+
+ if ( !$str ) {
+ if ( $this->use_mysqli ) {
+ $str = mysqli_error( $this->dbh );
+ } else {
+ $str = mysql_error( $this->dbh );
+ }
+ }
+ $EZSQL_ERROR[] = array( 'query' => $this->last_query, 'error_str' => $str );
+
+ if ( $this->suppress_errors )
+ return false;
+
+ wp_load_translations_early();
+
+ if ( $caller = $this->get_caller() )
+ $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;
+
+ // 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 of errors. However,
+ * this function can be used to enable and disable showing of database
+ * errors.
+ *
+ * @since 0.71
+ * @see wpdb::hide_errors()
+ *
+ * @param bool $show Whether to show or hide errors
+ * @return bool Old value for showing errors.
+ */
+ 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 active
+ */
+ public function hide_errors() {
+ $show = $this->show_errors;
+ $this->show_errors = false;
+ return $show;
+ }
+
+ /**
+ * Whether to suppress database errors.
+ *
+ * By default database errors are suppressed, with a simple
+ * call to this function they can be enabled.
+ *
+ * @since 2.5.0
+ * @see wpdb::hide_errors()
+ * @param bool $suppress Optional. New value. Defaults to true.
+ * @return bool Old value
+ */
+ public function suppress_errors( $suppress = true ) {
+ $errors = $this->suppress_errors;
+ $this->suppress_errors = (bool) $suppress;
+ return $errors;
+ }
+
+ /**
+ * Kill cached query results.
+ *
+ * @since 0.71
+ */
+ public function flush() {
+ $this->last_result = array();
+ $this->col_info = null;
+ $this->last_query = null;
+ $this->rows_affected = $this->num_rows = 0;
+ $this->last_error = '';
+
+ if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
+ mysqli_free_result( $this->result );
+ $this->result = null;
+
+ // Sanity 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 );
+ }
+ } elseif ( is_resource( $this->result ) ) {
+ mysql_free_result( $this->result );
+ }
+ }
+
+ /**
+ * Connect to and select 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;
+
+ /*
+ * Deprecated in 3.9+ when using MySQLi. No equivalent
+ * $new_link parameter exists for mysqli_* functions.
+ */
+ $new_link = defined( 'MYSQL_NEW_LINK' ) ? MYSQL_NEW_LINK : true;
+ $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;
+
+ if ( $this->use_mysqli ) {
+ $this->dbh = mysqli_init();
+
+ // 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 and/or socket file.
+ $port = null;
+ $socket = null;
+ $host = $this->dbhost;
+ $port_or_socket = strstr( $host, ':' );
+ if ( ! empty( $port_or_socket ) ) {
+ $host = substr( $host, 0, strpos( $host, ':' ) );
+ $port_or_socket = substr( $port_or_socket, 1 );
+ if ( 0 !== strpos( $port_or_socket, '/' ) ) {
+ $port = intval( $port_or_socket );
+ $maybe_socket = strstr( $port_or_socket, ':' );
+ if ( ! empty( $maybe_socket ) ) {
+ $socket = substr( $maybe_socket, 1 );
+ }
+ } else {
+ $socket = $port_or_socket;
+ }
+ }
+
+ if ( WP_DEBUG ) {
+ mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
+ } else {
+ @mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
+ }
+
+ if ( $this->dbh->connect_errno ) {
+ $this->dbh = null;
+
+ /* It's possible ext/mysqli is misconfigured. Fall back to ext/mysql if:
+ * - We haven't previously connected, and
+ * - WP_USE_EXT_MYSQL isn't set to false, and
+ * - ext/mysql is loaded.
+ */
+ $attempt_fallback = true;
+
+ if ( $this->has_connected ) {
+ $attempt_fallback = false;
+ } elseif ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) {
+ $attempt_fallback = false;
+ } elseif ( ! function_exists( 'mysql_connect' ) ) {
+ $attempt_fallback = false;
+ }
+
+ if ( $attempt_fallback ) {
+ $this->use_mysqli = false;
+ return $this->db_connect( $allow_bail );
+ }
+ }
+ } else {
+ if ( WP_DEBUG ) {
+ $this->dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
+ } else {
+ $this->dbh = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
+ }
+ }
+
+ 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' );
+ die();
+ }
+
+ $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 we can’t contact the database server at %2$s. This could mean your host’s database server is down.' ),
+ '<code>wp-config.php</code>',
+ '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
+ ) . "</p>\n";
+
+ $message .= "<ul>\n";
+ $message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n";
+ $message .= '<li>' . __( 'Are you sure that you have typed the correct hostname?' ) . "</li>\n";
+ $message .= '<li>' . __( 'Are you sure that the database server is running?' ) . "</li>\n";
+ $message .= "</ul>\n";
+
+ $message .= '<p>' . sprintf(
+ /* translators: %s: support forums URL */
+ __( 'If you’re 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/' )
+ ) . "</p>\n";
+
+ $this->bail( $message, 'db_connect_fail' );
+
+ return false;
+ } elseif ( $this->dbh ) {
+ if ( ! $this->has_connected ) {
+ $this->init_charset();
+ }
+
+ $this->has_connected = true;
+
+ $this->set_charset( $this->dbh );
+
+ $this->ready = true;
+ $this->set_sql_mode();
+ $this->select( $this->dbname, $this->dbh );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 after the
+ * 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.
+ *
+ * @since 3.9.0
+ *
+ * @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 ( $this->use_mysqli ) {
+ if ( ! empty( $this->dbh ) && mysqli_ping( $this->dbh ) ) {
+ return true;
+ }
+ } else {
+ if ( ! empty( $this->dbh ) && mysql_ping( $this->dbh ) ) {
+ return true;
+ }
+ }
+
+ $error_reporting = false;
+
+ // Disable warnings, as we don't want to see a multitude of "unable to connect" messages
+ if ( WP_DEBUG ) {
+ $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 );
+ }
+
+ return true;
+ }
+
+ sleep( 1 );
+ }
+
+ // 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' ) ) {
+ return false;
+ }
+
+ if ( ! $allow_bail ) {
+ return false;
+ }
+
+ wp_load_translations_early();
+
+ $message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n";
+
+ $message .= '<p>' . sprintf(
+ /* translators: %s: database host */
+ __( 'This means that we lost contact with the database server at %s. This could mean your host’s database server is down.' ),
+ '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
+ ) . "</p>\n";
+
+ $message .= "<ul>\n";
+ $message .= '<li>' . __( 'Are you sure that the database server is running?' ) . "</li>\n";
+ $message .= '<li>' . __( 'Are you sure that the database server is not under particularly heavy load?' ) . "</li>\n";
+ $message .= "</ul>\n";
+
+ $message .= '<p>' . sprintf(
+ /* translators: %s: support forums URL */
+ __( 'If you’re 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/' )
+ ) . "</p>\n";
+
+ // 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).
+ dead_db();
+ }
+
+ /**
+ * Perform a MySQL database query, using current database connection.
+ *
+ * More information can be found on the codex page.
+ *
+ * @since 0.71
+ *
+ * @param string $query Database query
+ * @return int|false Number of rows affected/selected or false on error
+ */
+ public function query( $query ) {
+ if ( ! $this->ready ) {
+ $this->check_current_query = true;
+ return false;
+ }
+
+ /**
+ * Filters the database query.
+ *
+ * Some queries are made before the plugins have been loaded,
+ * and thus cannot be filtered with this method.
+ *
+ * @since 2.1.0
+ *
+ * @param string $query Database query.
+ */
+ $query = apply_filters( 'query', $query );
+
+ $this->flush();
+
+ // 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.
+ $this->flush();
+ if ( $stripped_query !== $query ) {
+ $this->insert_id = 0;
+ return false;
+ }
+ }
+
+ $this->check_current_query = true;
+
+ // Keep track of the last query for debug.
+ $this->last_query = $query;
+
+ $this->_do_query( $query );
+
+ // MySQL server has gone away, try to reconnect.
+ $mysql_errno = 0;
+ if ( ! empty( $this->dbh ) ) {
+ if ( $this->use_mysqli ) {
+ if ( $this->dbh instanceof mysqli ) {
+ $mysql_errno = mysqli_errno( $this->dbh );
+ } else {
+ // $dbh is defined, but isn't a real connection.
+ // Something has gone horribly wrong, let's try a reconnect.
+ $mysql_errno = 2006;
+ }
+ } else {
+ if ( is_resource( $this->dbh ) ) {
+ $mysql_errno = mysql_errno( $this->dbh );
+ } else {
+ $mysql_errno = 2006;
+ }
+ }
+ }
+
+ if ( empty( $this->dbh ) || 2006 == $mysql_errno ) {
+ if ( $this->check_connection() ) {
+ $this->_do_query( $query );
+ } else {
+ $this->insert_id = 0;
+ return false;
+ }
+ }
+
+ // If there is an error then take note of it.
+ if ( $this->use_mysqli ) {
+ if ( $this->dbh instanceof mysqli ) {
+ $this->last_error = mysqli_error( $this->dbh );
+ } else {
+ $this->last_error = __( 'Unable to retrieve the error message from MySQL' );
+ }
+ } else {
+ if ( is_resource( $this->dbh ) ) {
+ $this->last_error = mysql_error( $this->dbh );
+ } else {
+ $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 ) )
+ $this->insert_id = 0;
+
+ $this->print_error();
+ return false;
+ }
+
+ 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 ) ) {
+ if ( $this->use_mysqli ) {
+ $this->rows_affected = mysqli_affected_rows( $this->dbh );
+ } else {
+ $this->rows_affected = mysql_affected_rows( $this->dbh );
+ }
+ // Take note of the insert_id
+ if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
+ if ( $this->use_mysqli ) {
+ $this->insert_id = mysqli_insert_id( $this->dbh );
+ } else {
+ $this->insert_id = mysql_insert_id( $this->dbh );
+ }
+ }
+ // Return number of rows affected
+ $return_val = $this->rows_affected;
+ } else {
+ $num_rows = 0;
+ if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
+ while ( $row = mysqli_fetch_object( $this->result ) ) {
+ $this->last_result[$num_rows] = $row;
+ $num_rows++;
+ }
+ } elseif ( is_resource( $this->result ) ) {
+ while ( $row = mysql_fetch_object( $this->result ) ) {
+ $this->last_result[$num_rows] = $row;
+ $num_rows++;
+ }
+ }
+
+ // Log number of rows the query returned
+ // and return number of rows selected
+ $this->num_rows = $num_rows;
+ $return_val = $num_rows;
+ }
+
+ return $return_val;
+ }
+
+ /**
+ * Internal function to perform the mysql_query() call.
+ *
+ * @since 3.9.0
+ *
+ * @access private
+ * @see wpdb::query()
+ *
+ * @param string $query The query to run.
+ */
+ private function _do_query( $query ) {
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ $this->timer_start();
+ }
+
+ if ( ! empty( $this->dbh ) && $this->use_mysqli ) {
+ $this->result = mysqli_query( $this->dbh, $query );
+ } elseif ( ! empty( $this->dbh ) ) {
+ $this->result = mysql_query( $query, $this->dbh );
+ }
+ $this->num_queries++;
+
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ $this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() );
+ }
+ }
+
+ /**
+ * Insert a row into a table.
+ *
+ * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
+ * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
+ *
+ * @since 2.5.0
+ * @see wpdb::prepare()
+ * @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 array|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.
+ * @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' );
+ }
+
+ /**
+ * Replace a row into a table.
+ *
+ * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
+ * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
+ *
+ * @since 3.0.0
+ * @see wpdb::prepare()
+ * @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 array|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.
+ * @return int|false The number of rows affected, or false on error.
+ */
+ public function replace( $table, $data, $format = null ) {
+ return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' );
+ }
+
+ /**
+ * Helper function for insert and replace.
+ *
+ * Runs an insert or replace query based on $type argument.
+ *
+ * @access private
+ * @since 3.0.0
+ * @see wpdb::prepare()
+ * @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 array|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.
+ * @param string $type Optional. What type of operation is this? INSERT or REPLACE. Defaults to INSERT.
+ * @return int|false The number of rows affected, or false on error.
+ */
+ function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
+ $this->insert_id = 0;
+
+ if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
+ return false;
+ }
+
+ $data = $this->process_fields( $table, $data, $format );
+ if ( false === $data ) {
+ return false;
+ }
+
+ $formats = $values = array();
+ foreach ( $data as $value ) {
+ if ( is_null( $value['value'] ) ) {
+ $formats[] = 'NULL';
+ continue;
+ }
+
+ $formats[] = $value['format'];
+ $values[] = $value['value'];
+ }
+
+ $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`';
+ $formats = implode( ', ', $formats );
+
+ $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
+
+ $this->check_current_query = false;
+ return $this->query( $this->prepare( $sql, $values ) );
+ }
+
+ /**
+ * Update a row in the table
+ *
+ * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 'bar' ), array( 'ID' => 1 ) )
+ * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( 'ID' => 1 ), array( '%s', '%d' ), array( '%d' ) )
+ *
+ * @since 2.5.0
+ * @see wpdb::prepare()
+ * @see wpdb::$field_types
+ * @see wp_set_wpdb_vars()
+ *
+ * @param string $table Table name
+ * @param array $data Data to update (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 array $where A named array of WHERE clauses (in column => value pairs).
+ * Multiple clauses will be joined with ANDs.
+ * Both $where columns and $where values should be "raw".
+ * Sending a null value will create an IS NULL comparison - the corresponding format will be ignored in this case.
+ * @param array|string $format Optional. An array of formats to be mapped to each of the values 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.
+ * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
+ * If string, that format will be used for all of the items in $where.
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
+ * If omitted, all values in $where will be treated as strings.
+ * @return int|false The number of rows updated, or false on error.
+ */
+ public function update( $table, $data, $where, $format = null, $where_format = null ) {
+ if ( ! is_array( $data ) || ! is_array( $where ) ) {
+ return false;
+ }
+
+ $data = $this->process_fields( $table, $data, $format );
+ if ( false === $data ) {
+ return false;
+ }
+ $where = $this->process_fields( $table, $where, $where_format );
+ if ( false === $where ) {
+ return false;
+ }
+
+ $fields = $conditions = $values = array();
+ foreach ( $data as $field => $value ) {
+ if ( is_null( $value['value'] ) ) {
+ $fields[] = "`$field` = NULL";
+ continue;
+ }
+
+ $fields[] = "`$field` = " . $value['format'];
+ $values[] = $value['value'];
+ }
+ foreach ( $where as $field => $value ) {
+ if ( is_null( $value['value'] ) ) {
+ $conditions[] = "`$field` IS NULL";
+ continue;
+ }
+
+ $conditions[] = "`$field` = " . $value['format'];
+ $values[] = $value['value'];
+ }
+
+ $fields = implode( ', ', $fields );
+ $conditions = implode( ' AND ', $conditions );
+
+ $sql = "UPDATE `$table` SET $fields WHERE $conditions";
+
+ $this->check_current_query = false;
+ return $this->query( $this->prepare( $sql, $values ) );
+ }
+
+ /**
+ * Delete a row in the table
+ *
+ * wpdb::delete( 'table', array( 'ID' => 1 ) )
+ * wpdb::delete( 'table', array( 'ID' => 1 ), array( '%d' ) )
+ *
+ * @since 3.4.0
+ * @see wpdb::prepare()
+ * @see wpdb::$field_types
+ * @see wp_set_wpdb_vars()
+ *
+ * @param string $table Table name
+ * @param array $where A named array of WHERE clauses (in column => value pairs).
+ * Multiple clauses will be joined with ANDs.
+ * Both $where columns and $where values should be "raw".
+ * Sending a null value will create an IS NULL comparison - the corresponding format will be ignored in this case.
+ * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
+ * If string, that format will be used for all of the items in $where.
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
+ * If omitted, all values in $where will be treated as strings unless otherwise specified in wpdb::$field_types.
+ * @return int|false The number of rows updated, or false on error.
+ */
+ public function delete( $table, $where, $where_format = null ) {
+ if ( ! is_array( $where ) ) {
+ return false;
+ }
+
+ $where = $this->process_fields( $table, $where, $where_format );
+ if ( false === $where ) {
+ return false;
+ }
+
+ $conditions = $values = array();
+ foreach ( $where as $field => $value ) {
+ if ( is_null( $value['value'] ) ) {
+ $conditions[] = "`$field` IS NULL";
+ continue;
+ }
+
+ $conditions[] = "`$field` = " . $value['format'];
+ $values[] = $value['value'];
+ }
+
+ $conditions = implode( ' AND ', $conditions );
+
+ $sql = "DELETE FROM `$table` WHERE $conditions";
+
+ $this->check_current_query = false;
+ return $this->query( $this->prepare( $sql, $values ) );
+ }
+
+ /**
+ * Processes arrays of field/value pairs and field formats.
+ *
+ * This is a helper method for wpdb's CRUD methods, which take field/value
+ * pairs for inserts, updates, and where clauses. This method first pairs
+ * each value with a format. Then it determines the charset of that field,
+ * using that to determine if any invalid text would be stripped. If text is
+ * stripped, then field processing is rejected and the query fails.
+ *
+ * @since 4.2.0
+ * @access protected
+ *
+ * @param string $table Table name.
+ * @param array $data Field/value pair.
+ * @param mixed $format Format for each field.
+ * @return array|false Returns an array of fields that contain paired values
+ * and formats. Returns false for invalid values.
+ */
+ protected function process_fields( $table, $data, $format ) {
+ $data = $this->process_field_formats( $data, $format );
+ if ( false === $data ) {
+ return false;
+ }
+
+ $data = $this->process_field_charsets( $data, $table );
+ if ( false === $data ) {
+ return false;
+ }
+
+ $data = $this->process_field_lengths( $data, $table );
+ if ( false === $data ) {
+ return false;
+ }
+
+ $converted_data = $this->strip_invalid_text( $data );
+
+ if ( $data !== $converted_data ) {
+ return false;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Prepares arrays of value/format pairs as passed to wpdb CRUD methods.
+ *
+ * @since 4.2.0
+ * @access protected
+ *
+ * @param array $data Array of fields to values.
+ * @param mixed $format Formats to be mapped to the values in $data.
+ * @return array Array, keyed by field names with values being an array
+ * of 'value' and 'format' keys.
+ */
+ protected function process_field_formats( $data, $format ) {
+ $formats = $original_formats = (array) $format;
+
+ foreach ( $data as $field => $value ) {
+ $value = array(
+ 'value' => $value,
+ 'format' => '%s',
+ );
+
+ if ( ! empty( $format ) ) {
+ $value['format'] = array_shift( $formats );
+ if ( ! $value['format'] ) {
+ $value['format'] = reset( $original_formats );
+ }
+ } elseif ( isset( $this->field_types[ $field ] ) ) {
+ $value['format'] = $this->field_types[ $field ];
+ }
+
+ $data[ $field ] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Adds field charsets to field/value/format arrays generated by
+ * the wpdb::process_field_formats() method.
+ *
+ * @since 4.2.0
+ * @access protected
+ *
+ * @param array $data As it comes from the wpdb::process_field_formats() method.
+ * @param string $table Table name.
+ * @return array|false The same array as $data with additional 'charset' keys.
+ */
+ protected function process_field_charsets( $data, $table ) {
+ foreach ( $data as $field => $value ) {
+ if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
+ /*
+ * We can skip this field if we know it isn't a string.
+ * This checks %d/%f versus ! %s because its sprintf() could take more.
+ */
+ $value['charset'] = false;
+ } else {
+ $value['charset'] = $this->get_col_charset( $table, $field );
+ if ( is_wp_error( $value['charset'] ) ) {
+ return false;
+ }
+ }
+
+ $data[ $field ] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * For string fields, record the maximum string length that field can safely save.
+ *
+ * @since 4.2.1
+ * @access protected
+ *
+ * @param array $data As it comes from the wpdb::process_field_charsets() method.
+ * @param string $table Table name.
+ * @return array|false The same array as $data with additional 'length' keys, or false if
+ * any of the values were too long for their corresponding field.
+ */
+ protected function process_field_lengths( $data, $table ) {
+ foreach ( $data as $field => $value ) {
+ if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
+ /*
+ * We can skip this field if we know it isn't a string.
+ * This checks %d/%f versus ! %s because its sprintf() could take more.
+ */
+ $value['length'] = false;
+ } else {
+ $value['length'] = $this->get_col_length( $table, $field );
+ if ( is_wp_error( $value['length'] ) ) {
+ return false;
+ }
+ }
+
+ $data[ $field ] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Retrieve one variable from the database.
+ *
+ * Executes a SQL query and returns the value from the SQL result.
+ * If the SQL result contains more than one column and/or more than one row, this function returns the value in the column and row specified.
+ * If $query is null, this function returns the value in the specified column and row from the previous SQL result.
+ *
+ * @since 0.71
+ *
+ * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
+ * @param int $x Optional. Column of value to return. Indexed from 0.
+ * @param int $y Optional. Row of value to return. Indexed from 0.
+ * @return string|null Database query result (as string), or null on failure
+ */
+ public function get_var( $query = null, $x = 0, $y = 0 ) {
+ $this->func_call = "\$db->get_var(\"$query\", $x, $y)";
+
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
+ $this->check_current_query = false;
+ }
+
+ if ( $query ) {
+ $this->query( $query );
+ }
+
+ // Extract var out of cached results based x,y vals
+ if ( !empty( $this->last_result[$y] ) ) {
+ $values = array_values( get_object_vars( $this->last_result[$y] ) );
+ }
+
+ // If there is a value return it else return null
+ return ( isset( $values[$x] ) && $values[$x] !== '' ) ? $values[$x] : null;
+ }
+
+ /**
+ * Retrieve one row from the database.
+ *
+ * Executes a SQL query and returns the row from the SQL result.
+ *
+ * @since 0.71
+ *
+ * @param string|null $query SQL query.
+ * @param string $output Optional. one of ARRAY_A | ARRAY_N | OBJECT constants.
+ * Return an associative array (column => value, ...),
+ * a numerically indexed array (0 => value, ...) or
+ * an object ( ->column = value ), respectively.
+ * @param int $y Optional. Row to return. Indexed from 0.
+ * @return array|object|null|void Database query result in format specified by $output or null on failure
+ */
+ public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
+ $this->func_call = "\$db->get_row(\"$query\",$output,$y)";
+
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
+ $this->check_current_query = false;
+ }
+
+ if ( $query ) {
+ $this->query( $query );
+ } else {
+ return null;
+ }
+
+ if ( !isset( $this->last_result[$y] ) )
+ return null;
+
+ if ( $output == OBJECT ) {
+ return $this->last_result[$y] ? $this->last_result[$y] : null;
+ } elseif ( $output == ARRAY_A ) {
+ return $this->last_result[$y] ? get_object_vars( $this->last_result[$y] ) : null;
+ } elseif ( $output == ARRAY_N ) {
+ return $this->last_result[$y] ? array_values( get_object_vars( $this->last_result[$y] ) ) : null;
+ } elseif ( strtoupper( $output ) === OBJECT ) {
+ // Back compat for OBJECT being previously case insensitive.
+ return $this->last_result[$y] ? $this->last_result[$y] : null;
+ } else {
+ $this->print_error( " \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N" );
+ }
+ }
+
+ /**
+ * Retrieve one column from the database.
+ *
+ * Executes a SQL query and returns the column from the SQL result.
+ * If the SQL result contains more than one column, this function returns the column specified.
+ * If $query is null, this function returns the specified column from the previous SQL result.
+ *
+ * @since 0.71
+ *
+ * @param string|null $query Optional. SQL query. Defaults to previous query.
+ * @param int $x Optional. Column to return. Indexed from 0.
+ * @return array Database query result. Array indexed from 0 by SQL result row number.
+ */
+ public function get_col( $query = null , $x = 0 ) {
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
+ $this->check_current_query = false;
+ }
+
+ if ( $query ) {
+ $this->query( $query );
+ }
+
+ $new_array = array();
+ // Extract the column values
+ for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
+ $new_array[$i] = $this->get_var( null, $x, $i );
+ }
+ return $new_array;
+ }
+
+ /**
+ * Retrieve an entire SQL result set from the database (i.e., many rows)
+ *
+ * Executes a SQL query and returns the entire SQL result.
+ *
+ * @since 0.71
+ *
+ * @param string $query SQL query.
+ * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
+ * With one of the first three, return an array of rows indexed from 0 by SQL result row number.
+ * Each row is an associative array (column => value, ...), a numerically indexed array (0 => value, ...), or an object. ( ->column = value ), respectively.
+ * With OBJECT_K, return an associative array of row objects keyed by the value of each row's first column's value.
+ * Duplicate keys are discarded.
+ * @return array|object|null Database query results
+ */
+ public function get_results( $query = null, $output = OBJECT ) {
+ $this->func_call = "\$db->get_results(\"$query\", $output)";
+
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
+ $this->check_current_query = false;
+ }
+
+ if ( $query ) {
+ $this->query( $query );
+ } else {
+ return null;
+ }
+
+ $new_array = array();
+ if ( $output == OBJECT ) {
+ // Return an integer-keyed array of row objects
+ return $this->last_result;
+ } elseif ( $output == OBJECT_K ) {
+ // Return an array of row objects with keys from column 1
+ // (Duplicates are discarded)
+ foreach ( $this->last_result as $row ) {
+ $var_by_ref = get_object_vars( $row );
+ $key = array_shift( $var_by_ref );
+ if ( ! isset( $new_array[ $key ] ) )
+ $new_array[ $key ] = $row;
+ }
+ return $new_array;
+ } elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
+ // Return an integer-keyed array of...
+ if ( $this->last_result ) {
+ foreach ( (array) $this->last_result as $row ) {
+ if ( $output == ARRAY_N ) {
+ // ...integer-keyed row arrays
+ $new_array[] = array_values( get_object_vars( $row ) );
+ } else {
+ // ...column name-keyed row arrays
+ $new_array[] = get_object_vars( $row );
+ }
+ }
+ }
+ return $new_array;
+ } elseif ( strtoupper( $output ) === OBJECT ) {
+ // Back compat for OBJECT being previously case insensitive.
+ return $this->last_result;
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the character set for the given table.
+ *
+ * @since 4.2.0
+ * @access protected
+ *
+ * @param string $table Table name.
+ * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
+ */
+ protected function get_table_charset( $table ) {
+ $tablekey = strtolower( $table );
+
+ /**
+ * Filters the table charset value before the DB is checked.
+ *
+ * Passing a non-null value to the filter will effectively short-circuit
+ * checking the DB for the charset, returning that value instead.
+ *
+ * @since 4.2.0
+ *
+ * @param string $charset The character set to use. Default null.
+ * @param string $table The name of the table being checked.
+ */
+ $charset = apply_filters( 'pre_get_table_charset', null, $table );
+ if ( null !== $charset ) {
+ return $charset;
+ }
+
+ if ( isset( $this->table_charset[ $tablekey ] ) ) {
+ return $this->table_charset[ $tablekey ];
+ }
+
+ $charsets = $columns = array();
+
+ $table_parts = explode( '.', $table );
+ $table = '`' . implode( '`.`', $table_parts ) . '`';
+ $results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
+ if ( ! $results ) {
+ return new WP_Error( 'wpdb_get_table_charset_failure' );
+ }
+
+ foreach ( $results as $column ) {
+ $columns[ strtolower( $column->Field ) ] = $column;
+ }
+
+ $this->col_meta[ $tablekey ] = $columns;
+
+ foreach ( $columns as $column ) {
+ if ( ! empty( $column->Collation ) ) {
+ list( $charset ) = explode( '_', $column->Collation );
+
+ // If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters.
+ if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
+ $charset = 'utf8';
+ }
+
+ $charsets[ strtolower( $charset ) ] = true;
+ }
+
+ list( $type ) = explode( '(', $column->Type );
+
+ // A binary/blob means the whole query gets treated like this.
+ if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) {
+ $this->table_charset[ $tablekey ] = 'binary';
+ return 'binary';
+ }
+ }
+
+ // utf8mb3 is an alias for utf8.
+ if ( isset( $charsets['utf8mb3'] ) ) {
+ $charsets['utf8'] = true;
+ unset( $charsets['utf8mb3'] );
+ }
+
+ // Check if we have more than one charset in play.
+ $count = count( $charsets );
+ if ( 1 === $count ) {
+ $charset = key( $charsets );
+ } elseif ( 0 === $count ) {
+ // No charsets, assume this table can store whatever.
+ $charset = false;
+ } else {
+ // More than one charset. Remove latin1 if present and recalculate.
+ unset( $charsets['latin1'] );
+ $count = count( $charsets );
+ if ( 1 === $count ) {
+ // Only one charset (besides latin1).
+ $charset = key( $charsets );
+ } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
+ // Two charsets, but they're utf8 and utf8mb4, use utf8.
+ $charset = 'utf8';
+ } else {
+ // Two mixed character sets. ascii.
+ $charset = 'ascii';
+ }
+ }
+
+ $this->table_charset[ $tablekey ] = $charset;
+ return $charset;
+ }
+
+ /**
+ * Retrieves the character set for the given column.
+ *
+ * @since 4.2.0
+ * @access public
+ *
+ * @param string $table Table name.
+ * @param string $column Column name.
+ * @return string|false|WP_Error Column character set as a string. False if the column has no
+ * character set. WP_Error object if there was an error.
+ */
+ public function get_col_charset( $table, $column ) {
+ $tablekey = strtolower( $table );
+ $columnkey = strtolower( $column );
+
+ /**
+ * Filters the column charset value before the DB is checked.
+ *
+ * Passing a non-null value to the filter will short-circuit
+ * checking the DB for the charset, returning that value instead.
+ *
+ * @since 4.2.0
+ *
+ * @param string $charset The character set to use. Default null.
+ * @param string $table The name of the table being checked.
+ * @param string $column The name of the column being checked.
+ */
+ $charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
+ if ( null !== $charset ) {
+ return $charset;
+ }
+
+ // Skip this entirely if this isn't a MySQL database.
+ if ( empty( $this->is_mysql ) ) {
+ return false;
+ }
+
+ if ( empty( $this->table_charset[ $tablekey ] ) ) {
+ // This primes column information for us.
+ $table_charset = $this->get_table_charset( $table );
+ if ( is_wp_error( $table_charset ) ) {
+ return $table_charset;
+ }
+ }
+
+ // If still no column information, return the table charset.
+ if ( empty( $this->col_meta[ $tablekey ] ) ) {
+ return $this->table_charset[ $tablekey ];
+ }
+
+ // If this column doesn't exist, return the table charset.
+ if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
+ return $this->table_charset[ $tablekey ];
+ }
+
+ // Return false when it's not a string column.
+ if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
+ return false;
+ }
+
+ list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
+ return $charset;
+ }
+
+ /**
+ * Retrieve the maximum string length allowed in a given column.
+ * The length may either be specified as a byte length or a character length.
+ *
+ * @since 4.2.1
+ * @access public
+ *
+ * @param string $table Table name.
+ * @param string $column Column name.
+ * @return array|false|WP_Error array( 'length' => (int), 'type' => 'byte' | 'char' )
+ * false if the column has no length (for example, numeric column)
+ * WP_Error object if there was an error.
+ */
+ public function get_col_length( $table, $column ) {
+ $tablekey = strtolower( $table );
+ $columnkey = strtolower( $column );
+
+ // Skip this entirely if this isn't a MySQL database.
+ if ( empty( $this->is_mysql ) ) {
+ return false;
+ }
+
+ if ( empty( $this->col_meta[ $tablekey ] ) ) {
+ // This primes column information for us.
+ $table_charset = $this->get_table_charset( $table );
+ if ( is_wp_error( $table_charset ) ) {
+ return $table_charset;
+ }
+ }
+
+ if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
+ return false;
+ }
+
+ $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
+
+ $type = strtolower( $typeinfo[0] );
+ if ( ! empty( $typeinfo[1] ) ) {
+ $length = trim( $typeinfo[1], ')' );
+ } else {
+ $length = false;
+ }
+
+ switch( $type ) {
+ case 'char':
+ case 'varchar':
+ return array(
+ 'type' => 'char',
+ 'length' => (int) $length,
+ );
+
+ case 'binary':
+ case 'varbinary':
+ return array(
+ 'type' => 'byte',
+ 'length' => (int) $length,
+ );
+
+ case 'tinyblob':
+ case 'tinytext':
+ return array(
+ 'type' => 'byte',
+ 'length' => 255, // 2^8 - 1
+ );
+
+ case 'blob':
+ case 'text':
+ return array(
+ 'type' => 'byte',
+ 'length' => 65535, // 2^16 - 1
+ );
+
+ case 'mediumblob':
+ case 'mediumtext':
+ return array(
+ 'type' => 'byte',
+ 'length' => 16777215, // 2^24 - 1
+ );
+
+ case 'longblob':
+ case 'longtext':
+ return array(
+ 'type' => 'byte',
+ 'length' => 4294967295, // 2^32 - 1
+ );
+
+ default:
+ return false;
+ }