'%d' * * @since 2.8.0 * @see wpdb::prepare() * @see wpdb::insert() * @see wpdb::update() * @see wpdb::delete() * @see wp_set_wpdb_vars() * @access public * @var array */ var $field_types = array(); /** * Database table columns charset * * @since 2.2.0 * @access public * @var string */ var $charset; /** * Database table columns collate * * @since 2.2.0 * @access public * @var string */ var $collate; /** * Database Username * * @since 2.9.0 * @access protected * @var string */ protected $dbuser; /** * Database Password * * @since 3.1.0 * @access protected * @var string */ protected $dbpassword; /** * Database Name * * @since 3.1.0 * @access protected * @var string */ protected $dbname; /** * Database Host * * @since 3.1.0 * @access protected * @var string */ protected $dbhost; /** * Database Handle * * @since 0.71 * @access protected * @var string */ protected $dbh; /** * A textual description of the last query/get_row/get_var call * * @since 3.0.0 * @access public * @var string */ var $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 * @access public * @var bool */ public $is_mysql = null; /** * Connects to the database server and selects a database * * PHP5 style constructor for compatibility with PHP5. Does * the actual setting up of the class properties and connection * to the database. * * @link http://core.trac.wordpress.org/ticket/3354 * @since 2.0.8 * * @param string $dbuser MySQL database user * @param string $dbpassword MySQL database password * @param string $dbname MySQL database name * @param string $dbhost MySQL database host */ function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) { register_shutdown_function( array( $this, '__destruct' ) ); if ( WP_DEBUG && WP_DEBUG_DISPLAY ) $this->show_errors(); $this->init_charset(); $this->dbuser = $dbuser; $this->dbpassword = $dbpassword; $this->dbname = $dbname; $this->dbhost = $dbhost; $this->db_connect(); } /** * PHP5 style destructor and will run when database object is destroyed. * * @see wpdb::__construct() * @since 2.0.8 * @return bool true */ function __destruct() { return true; } /** * PHP5 style magic getter, used to lazy-load expensive data. * * @since 3.5.0 * * @param string $name The private member to get, and optionally process * @return mixed The private member */ function __get( $name ) { if ( 'col_info' == $name ) $this->load_col_info(); return $this->$name; } /** * Magic function, for backwards compatibility * * @since 3.5.0 * * @param string $name The private member to set * @param mixed $value The value to set */ function __set( $name, $value ) { $protected_members = array( 'col_meta', 'table_charset', 'check_current_query', ); if ( in_array( $name, $protected_members, true ) ) { return; } $this->$name = $value; } /** * Magic function, for backwards compatibility * * @since 3.5.0 * * @param string $name The private member to check * * @return bool If the member is set or not */ function __isset( $name ) { return isset( $this->$name ); } /** * Magic function, for backwards compatibility * * @since 3.5.0 * * @param string $name The private member to unset */ function __unset( $name ) { unset( $this->$name ); } /** * Set $this->charset and $this->collate * * @since 3.1.0 */ function init_charset() { if ( function_exists('is_multisite') && is_multisite() ) { $this->charset = 'utf8'; if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) $this->collate = DB_COLLATE; else $this->collate = 'utf8_general_ci'; } elseif ( defined( 'DB_COLLATE' ) ) { $this->collate = DB_COLLATE; } if ( defined( 'DB_CHARSET' ) ) $this->charset = DB_CHARSET; } /** * Sets the connection's character set. * * @since 3.1.0 * * @param resource $dbh The resource given by mysql_connect * @param string $charset The character set (optional) * @param string $collate The collation (optional) */ 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 ) ) { if ( function_exists( 'mysql_set_charset' ) && $this->has_cap( 'set_charset' ) ) { mysql_set_charset( $charset, $dbh ); } else { $query = $this->prepare( 'SET NAMES %s', $charset ); if ( ! empty( $collate ) ) $query .= $this->prepare( ' COLLATE %s', $collate ); mysql_query( $query, $dbh ); } } } /** * 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. * @return string|WP_Error Old prefix or WP_Error on error */ 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 * @access public * @param int $blog_id * @param int $site_id Optional. * @return string previous blog id */ function set_blog_id( $blog_id, $site_id = 0 ) { if ( ! empty( $site_id ) ) $this->siteid = $site_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. * * @uses is_multisite() * @since 3.0.0 * @param int $blog_id Optional. * @return string Blog prefix. */ 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 if running multisite. * 'ms_global' - returns the multisite global tables, regardless if current installation is multisite. * 'old' - returns tables which are deprecated. * * @since 3.0.0 * @uses wpdb::$tables * @uses wpdb::$old_tables * @uses wpdb::$global_tables * @uses wpdb::$ms_global_tables * @uses is_multisite() * * @param string $scope Optional. Can be all, global, ms_global, blog, or old tables. Defaults to all. * @param bool $prefix Optional. Whether to include table prefixes. Default true. If blog * prefix is requested, then the custom users and usermeta tables will be mapped. * @param int $blog_id Optional. The blog_id to prefix. Defaults to wpdb::$blogid. Used only when prefix is requested. * @return array Table names. When a prefix is requested, the key is the unprefixed table name. */ 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; break; default : return array(); break; } 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 ) ) $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 database connection. * * The database name will be changed based on the current database * connection. On failure, the execution will bail and display an DB error. * * @since 0.71 * * @param string $db MySQL database name * @param resource $dbh Optional link identifier. * @return null Always null. */ function select( $db, $dbh = null ) { if ( is_null($dbh) ) $dbh = $this->dbh; if ( !@mysql_select_db( $db, $dbh ) ) { $this->ready = false; wp_load_translations_early(); $this->bail( sprintf( __( '

Can’t select database

We were able to connect to the database server (which means your username and password is okay) but not able to select the %1$s database.

If you don\'t know how to set up a database you should contact your host. If all else fails you may find help at the WordPress Support Forums.

' ), htmlspecialchars( $db, ENT_QUOTES ), htmlspecialchars( $this->dbuser, ENT_QUOTES ) ), 'db_select_fail' ); return; } } /** * Do not use, deprecated. * * Use esc_sql() or wpdb::prepare() instead. * * @since 2.8.0 * @deprecated 3.6.0 * @see wpdb::prepare * @see esc_sql() * @access private * * @param string $string * @return string */ function _weak_escape( $string ) { if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) _deprecated_function( __METHOD__, '3.6', 'wpdb::prepare() or esc_sql()' ); return addslashes( $string ); } /** * Real escape, using mysql_real_escape_string() * * @see mysql_real_escape_string() * @since 2.8.0 * @access private * * @param string $string to escape * @return string escaped */ function _real_escape( $string ) { if ( $this->dbh ) return mysql_real_escape_string( $string, $this->dbh ); $class = get_class( $this ); _doing_it_wrong( $class, "$class must set a database connection for use with escaping.", E_USER_NOTICE ); return addslashes( $string ); } /** * Escape data. Works on arrays. * * @uses wpdb::_real_escape() * @since 2.8.0 * @access private * * @param string|array $data * @return string|array escaped */ 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 * @see wpdb::prepare() * @see esc_sql() * * @param mixed $data * @return mixed */ function escape( $data ) { if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) _deprecated_function( __METHOD__, '3.6', '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 $string to escape * @return void */ function escape_by_ref( &$string ) { if ( ! is_float( $string ) ) $string = $this->_real_escape( $string ); } /** * Prepares a SQL query for safe execution. Uses sprintf()-like syntax. * * The following directives can be used in the query format string: * %d (integer) * %f (float) * %s (string) * %% (literal percentage sign - no argument needed) * * All of %d, %f, and %s are to be left unquoted in the query string and they need an argument passed for them. * Literals (%) as parts of the query must be properly written as %%. * * This function only supports a small subset of the sprintf syntax; it only supports %d (integer), %f (float), and %s (string). * Does not support sign, padding, alignment, width or precision specifiers. * Does not support argument numbering/swapping. * * May be called like {@link http://php.net/sprintf sprintf()} or like {@link http://php.net/vsprintf vsprintf()}. * * Both %d and %s should be left unquoted in the query string. * * * wpdb::prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d", 'foo', 1337 ) * wpdb::prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' ); * * * @link http://php.net/sprintf Description of syntax. * @since 2.3.0 * * @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 like * {@link http://php.net/vsprintf vsprintf()}, or the first variable to substitute into the query's placeholders if * being called like {@link http://php.net/sprintf sprintf()}. * @param mixed $args,... further variables to substitute into the query's placeholders if being called like * {@link http://php.net/sprintf sprintf()}. * @return null|false|string Sanitized query string, null if there is no query, false if there is an error and string * if there was something to prepare */ function prepare( $query, $args ) { if ( is_null( $query ) ) return; $args = func_get_args(); array_shift( $args ); // If args were passed as an array (as in vsprintf), move them up if ( isset( $args[0] ) && is_array($args[0]) ) $args = $args[0]; $query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it $query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting $query = preg_replace( '|(?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 = "WordPress database error: [$str]\n{$this->last_query}\n"; 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 ); print "

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. */ 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 */ 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 */ function suppress_errors( $suppress = true ) { $errors = $this->suppress_errors; $this->suppress_errors = (bool) $suppress; return $errors; } /** * Kill cached query results. * * @since 0.71 * @return void */ 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 ( is_resource( $this->result ) ) mysql_free_result( $this->result ); } /** * Connect to and select database * * @since 3.0.0 */ function db_connect() { $this->is_mysql = true; $new_link = defined( 'MYSQL_NEW_LINK' ) ? MYSQL_NEW_LINK : true; $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0; 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 ) { wp_load_translations_early(); $this->bail( sprintf( __( "

Error establishing a database connection

This either means that the username and password information in your wp-config.php file is incorrect or we can't contact the database server at %s. This could mean your host's database server is down.

If you're unsure what these terms mean you should probably contact your host. If you still need help you can always visit the WordPress Support Forums.

" ), htmlspecialchars( $this->dbhost, ENT_QUOTES ) ), 'db_connect_fail' ); return; } $this->set_charset( $this->dbh ); $this->ready = true; $this->select( $this->dbname, $this->dbh ); } /** * 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 */ function query( $query ) { if ( ! $this->ready ) { $this->check_current_query = true; return false; } /** * Filter 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 ); $return_val = 0; $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; if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) $this->timer_start(); $this->result = @mysql_query( $query, $this->dbh ); $this->num_queries++; if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) $this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() ); // If there is an error then take note of it.. if ( $this->last_error = mysql_error( $this->dbh ) ) { // 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 ) ) { $this->rows_affected = mysql_affected_rows( $this->dbh ); // Take note of the insert_id if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) { $this->insert_id = mysql_insert_id($this->dbh); } // Return number of rows affected $return_val = $this->rows_affected; } else { $num_rows = 0; 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; } /** * 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). * @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. */ 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). * @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. */ 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). * @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 ) { $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). * @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". * @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. */ 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 ) { $fields[] = "`$field` = " . $value['format']; $values[] = $value['value']; } foreach ( $where as $field => $value ) { $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". * @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. */ 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 ) { $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|bool 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 {@see wpdb::process_field_formats()} method. * * @since 4.2.0 * @access protected * * @param array $data As it comes from the {@see wpdb::process_field_formats()} method. * @param string $table Table name. * @return 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 it's 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 it's 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 */ function get_var( $query = null, $x = 0, $y = 0 ) { $this->func_call = "\$db->get_var(\"$query\", $x, $y)"; if ( $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 mixed Database query result in format specified by $output or null on failure */ function get_row( $query = null, $output = OBJECT, $y = 0 ) { $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; if ( $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; } 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. */ function get_col( $query = null , $x = 0 ) { if ( $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 mixed Database query results */ function get_results( $query = null, $output = OBJECT ) { $this->func_call = "\$db->get_results(\"$query\", $output)"; if ( $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; } 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, {@see WP_Error} object if it couldn't be found. */ protected function get_table_charset( $table ) { $tablekey = strtolower( $table ); /** * Filter 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 ); $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 mixed Column character set as a string. False if the column has no * character set. {@see WP_Error} object if there was an error. */ public function get_col_charset( $table, $column ) { $tablekey = strtolower( $table ); $columnkey = strtolower( $column ); /** * Filter 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 ( false === $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 mixed 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 ( false === $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, ); break; case 'binary': case 'varbinary': return array( 'type' => 'byte', 'length' => (int) $length, ); break; case 'tinyblob': case 'tinytext': return array( 'type' => 'byte', 'length' => 255, // 2^8 - 1 ); break; case 'blob': case 'text': return array( 'type' => 'byte', 'length' => 65535, // 2^16 - 1 ); break; case 'mediumblob': case 'mediumtext': return array( 'type' => 'byte', 'length' => 16777215, // 2^24 - 1 ); break; case 'longblob': case 'longtext': return array( 'type' => 'byte', 'length' => 4294967295, // 2^32 - 1 ); break; default: return false; } return false; } /** * Check if a string is ASCII. * * The negative regex is faster for non-ASCII strings, as it allows * the search to finish as soon as it encounters a non-ASCII character. * * @since 4.2.0 * @access protected * * @param string $string String to check. * @return bool True if ASCII, false if not. */ protected function check_ascii( $string ) { if ( function_exists( 'mb_check_encoding' ) ) { if ( mb_check_encoding( $string, 'ASCII' ) ) { return true; } } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) { return true; } return false; } /** * Check if the query is accessing a collation considered safe on the current version of MySQL. * * @since 4.2.0 * @access protected * * @param string $query The query to check. * @return bool True if the collation is safe, false if it isn't. */ protected function check_safe_collation( $query ) { if ( $this->checking_collation ) { return true; } // We don't need to check the collation for queries that don't read data. $query = ltrim( $query, "\r\n\t (" ); if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) { return true; } // All-ASCII queries don't need extra checking. if ( $this->check_ascii( $query ) ) { return true; } $table = $this->get_table_from_query( $query ); if ( ! $table ) { return false; } $this->checking_collation = true; $collation = $this->get_table_charset( $table ); $this->checking_collation = false; // Tables with no collation, or latin1 only, don't need extra checking. if ( false === $collation || 'latin1' === $collation ) { return true; } $table = strtolower( $table ); if ( empty( $this->col_meta[ $table ] ) ) { return false; } // If any of the columns don't have one of these collations, it needs more sanity checking. foreach( $this->col_meta[ $table ] as $col ) { if ( empty( $col->Collation ) ) { continue; } if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) { return false; } } return true; } /** * Strips any invalid characters based on value/charset pairs. * * @since 4.2.0 * @access protected * * @param array $data Array of value arrays. Each value array has the keys * 'value' and 'charset'. An optional 'ascii' key can be * set to false to avoid redundant ASCII checks. * @return array|WP_Error The $data parameter, with invalid characters removed from * each value. This works as a passthrough: any additional keys * such as 'field' are retained in each value array. If we cannot * remove invalid characters, a {@see WP_Error} object is returned. */ // If any of the columns don't have one of these collations, it needs more sanity checking. protected function strip_invalid_text( $data ) { $db_check_string = false; foreach ( $data as &$value ) { $charset = $value['charset']; if ( is_array( $value['length'] ) ) { $length = $value['length']['length']; } else { $length = false; } // There's no charset to work with. if ( false === $charset ) { continue; } // Column isn't a string. if ( ! is_string( $value['value'] ) ) { continue; } $truncate_by_byte_length = 'byte' === $value['length']['type']; $needs_validation = true; if ( // latin1 can store any byte sequence 'latin1' === $charset || // ASCII is always OK. ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) ) { $truncate_by_byte_length = true; $needs_validation = false; } if ( $truncate_by_byte_length ) { mbstring_binary_safe_encoding(); if ( false !== $length && strlen( $value['value'] ) > $length ) { $value['value'] = substr( $value['value'], 0, $length ); } reset_mbstring_encoding(); if ( ! $needs_validation ) { continue; } } // utf8 can be handled by regex, which is a bunch faster than a DB lookup. if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) { $regex = '/ ( (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 | [\xE1-\xEC][\x80-\xBF]{2} | \xED[\x80-\x9F][\x80-\xBF] | [\xEE-\xEF][\x80-\xBF]{2}'; if ( 'utf8mb4' === $charset ) { $regex .= ' | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 | [\xF1-\xF3][\x80-\xBF]{3} | \xF4[\x80-\x8F][\x80-\xBF]{2} '; } $regex .= '){1,40} # ...one or more times ) | . # anything else /x'; $value['value'] = preg_replace( $regex, '$1', $value['value'] ); if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) { $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' ); } continue; } // We couldn't use any local conversions, send it to the DB. $value['db'] = $db_check_string = true; } unset( $value ); // Remove by reference. if ( $db_check_string ) { $queries = array(); foreach ( $data as $col => $value ) { if ( ! empty( $value['db'] ) ) { if ( ! isset( $queries[ $value['charset'] ] ) ) { $queries[ $value['charset'] ] = array(); } // We're going to need to truncate by characters or bytes, depending on the length value we have. if ( 'byte' === $value['length']['type'] ) { // Split the CONVERT() calls by charset, so we can make sure the connection is right $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING binary ), %d ) USING {$value['charset']} )", $value['value'], $value['length']['length'] ); } else { $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] ); } unset( $data[ $col ]['db'] ); } } $connection_charset = $this->charset; foreach ( $queries as $charset => $query ) { if ( ! $query ) { continue; } // Change the charset to match the string(s) we're converting if ( $charset !== $connection_charset ) { $connection_charset = $charset; $this->set_charset( $this->dbh, $charset ); } $this->check_current_query = false; $sql = array(); foreach ( $query as $column => $column_query ) { $sql[] = $column_query . " AS x_$column"; } $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A ); if ( ! $row ) { $this->set_charset( $this->dbh, $connection_charset ); return new WP_Error( 'wpdb_strip_invalid_text_failure' ); } foreach ( array_keys( $query ) as $column ) { $data[ $column ]['value'] = $row["x_$column"]; } } // Don't forget to change the charset back! if ( $connection_charset !== $this->charset ) { $this->set_charset( $this->dbh ); } } return $data; } /** * Strips any invalid characters from the query. * * @since 4.2.0 * @access protected * * @param string $query Query to convert. * @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails. */ protected function strip_invalid_text_from_query( $query ) { // We don't need to check the collation for queries that don't read data. $trimmed_query = ltrim( $query, "\r\n\t (" ); if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) { return $query; } $table = $this->get_table_from_query( $query ); if ( $table ) { $charset = $this->get_table_charset( $table ); if ( is_wp_error( $charset ) ) { return $charset; } // We can't reliably strip text from tables containing binary/blob columns if ( 'binary' === $charset ) { return $query; } } else { $charset = $this->charset; } $data = array( 'value' => $query, 'charset' => $charset, 'ascii' => false, 'length' => false, ); $data = $this->strip_invalid_text( array( $data ) ); if ( is_wp_error( $data ) ) { return $data; } return $data[0]['value']; } /** * Strips any invalid characters from the string for a given table and column. * * @since 4.2.0 * @access public * * @param string $table Table name. * @param string $column Column name. * @param string $value The text to check. * @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails. */ public function strip_invalid_text_for_column( $table, $column, $value ) { if ( ! is_string( $value ) ) { return $value; } $charset = $this->get_col_charset( $table, $column ); if ( ! $charset ) { // Not a string column. return $value; } elseif ( is_wp_error( $charset ) ) { // Bail on real errors. return $charset; } $data = array( $column => array( 'value' => $value, 'charset' => $charset, 'length' => $this->get_col_length( $table, $column ), ) ); $data = $this->strip_invalid_text( $data ); if ( is_wp_error( $data ) ) { return $data; } return $data[ $column ]['value']; } /** * Find the first table name referenced in a query. * * @since 4.2.0 * @access protected * * @param string $query The query to search. * @return string|false $table The table name found, or false if a table couldn't be found. */ protected function get_table_from_query( $query ) { // Remove characters that can legally trail the table name. $query = rtrim( $query, ';/-#' ); // Allow (select...) union [...] style queries. Use the first query's table name. $query = ltrim( $query, "\r\n\t (" ); /* * Strip everything between parentheses except nested selects and use only 1,000 * chars of the query. */ $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', substr( $query, 0, 1000 ) ); // Quickly match most common queries. if ( preg_match( '/^\s*(?:' . 'SELECT.*?\s+FROM' . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?' . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?' . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?' . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?' . ')\s+((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)/is', $query, $maybe ) ) { return str_replace( '`', '', $maybe[1] ); } // SHOW TABLE STATUS and SHOW TABLES if ( preg_match( '/^\s*(?:' . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' . ')\W((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\W/is', $query, $maybe ) ) { return str_replace( '`', '', $maybe[1] ); } // Big pattern for the rest of the table-related queries. if ( preg_match( '/^\s*(?:' . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM' . '|DESCRIBE|DESC|EXPLAIN|HANDLER' . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?' . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE' . '|TRUNCATE(?:\s+TABLE)?' . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?' . '|ALTER(?:\s+IGNORE)?\s+TABLE' . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?' . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON' . '|DROP\s+INDEX.*\s+ON' . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE' . '|(?:GRANT|REVOKE).*ON\s+TABLE' . '|SHOW\s+(?:.*FROM|.*TABLE)' . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) { return str_replace( '`', '', $maybe[1] ); } return false; } /** * Load the column metadata from the last query. * * @since 3.5.0 * * @access protected */ protected function load_col_info() { if ( $this->col_info ) return; for ( $i = 0; $i < @mysql_num_fields( $this->result ); $i++ ) { $this->col_info[ $i ] = @mysql_fetch_field( $this->result, $i ); } } /** * Retrieve column metadata from the last query. * * @since 0.71 * * @param string $info_type Optional. Type one of name, table, def, max_length, not_null, primary_key, multiple_key, unique_key, numeric, blob, type, unsigned, zerofill * @param int $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length. 3: if the col is numeric. 4: col's type * @return mixed Column Results */ function get_col_info( $info_type = 'name', $col_offset = -1 ) { $this->load_col_info(); if ( $this->col_info ) { if ( $col_offset == -1 ) { $i = 0; $new_array = array(); foreach( (array) $this->col_info as $col ) { $new_array[$i] = $col->{$info_type}; $i++; } return $new_array; } else { return $this->col_info[$col_offset]->{$info_type}; } } } /** * Starts the timer, for debugging purposes. * * @since 1.5.0 * * @return true */ function timer_start() { $this->time_start = microtime( true ); return true; } /** * Stops the debugging timer. * * @since 1.5.0 * * @return float Total time spent on the query, in seconds */ function timer_stop() { return ( microtime( true ) - $this->time_start ); } /** * Wraps errors in a nice header and footer and dies. * * Will not die if wpdb::$show_errors is false. * * @since 1.5.0 * * @param string $message The Error message * @param string $error_code Optional. A Computer readable string to identify the error. * @return false|void */ function bail( $message, $error_code = '500' ) { if ( !$this->show_errors ) { if ( class_exists( 'WP_Error' ) ) $this->error = new WP_Error($error_code, $message); else $this->error = $message; return false; } wp_die($message); } /** * Whether MySQL database is at least the required minimum version. * * @since 2.5.0 * @uses $wp_version * @uses $required_mysql_version * * @return WP_Error */ function check_database_version() { global $wp_version, $required_mysql_version; // Make sure the server has the required MySQL version if ( version_compare($this->db_version(), $required_mysql_version, '<') ) return new WP_Error('database_version', sprintf( __( 'ERROR: WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version )); } /** * Whether the database supports collation. * * Called when WordPress is generating the table scheme. * * @since 2.5.0 * @deprecated 3.5.0 * @deprecated Use wpdb::has_cap( 'collation' ) * * @return bool True if collation is supported, false if version does not */ function supports_collation() { _deprecated_function( __FUNCTION__, '3.5', 'wpdb::has_cap( \'collation\' )' ); return $this->has_cap( 'collation' ); } /** * The database character collate. * * @since 3.5.0 * * @return string The database character collate. */ public function get_charset_collate() { $charset_collate = ''; if ( ! empty( $this->charset ) ) $charset_collate = "DEFAULT CHARACTER SET $this->charset"; if ( ! empty( $this->collate ) ) $charset_collate .= " COLLATE $this->collate"; return $charset_collate; } /** * Determine if a database supports a particular feature. * * @since 2.7.0 * @see wpdb::db_version() * * @param string $db_cap The feature to check for. * @return bool */ function has_cap( $db_cap ) { $version = $this->db_version(); switch ( strtolower( $db_cap ) ) { case 'collation' : // @since 2.5.0 case 'group_concat' : // @since 2.7.0 case 'subqueries' : // @since 2.7.0 return version_compare( $version, '4.1', '>=' ); case 'set_charset' : return version_compare( $version, '5.0.7', '>=' ); }; return false; } /** * Retrieve the name of the function that called wpdb. * * Searches up the list of functions until it reaches * the one that would most logically had called this method. * * @since 2.5.0 * * @return string The name of the calling function */ function get_caller() { return wp_debug_backtrace_summary( __CLASS__ ); } /** * The database version number. * * @since 2.7.0 * * @return false|string false on failure, version number on success */ function db_version() { return preg_replace( '/[^0-9.].*/', '', mysql_get_server_info( $this->dbh ) ); } }