diff -r 490d5cc509ed -r cf61fcea0001 wp/wp-includes/wp-db.php --- a/wp/wp-includes/wp-db.php Tue Jun 09 11:14:17 2015 +0000 +++ b/wp/wp-includes/wp-db.php Mon Oct 14 17:39:30 2019 +0200 @@ -45,8 +45,6 @@ * * @link https://codex.wordpress.org/Function_Reference/wpdb_Class * - * @package WordPress - * @subpackage Database * @since 0.71 */ class wpdb { @@ -58,7 +56,6 @@ * evaluated to true. * * @since 0.71 - * @access private * @var bool */ var $show_errors = false; @@ -66,7 +63,6 @@ /** * Whether to suppress errors during the DB bootstrapping. * - * @access private * @since 2.5.0 * @var bool */ @@ -84,25 +80,22 @@ * Amount of queries made * * @since 1.2.0 - * @access private * @var int */ - var $num_queries = 0; + public $num_queries = 0; /** * Count of rows returned by previous query * * @since 0.71 - * @access private * @var int */ - var $num_rows = 0; + public $num_rows = 0; /** * Count of affected rows by previous query * * @since 0.71 - * @access private * @var int */ var $rows_affected = 0; @@ -111,16 +104,14 @@ * The ID generated for an AUTO_INCREMENT column by the previous query (usually INSERT). * * @since 0.71 - * @access public * @var int */ - var $insert_id = 0; + public $insert_id = 0; /** * Last query made * * @since 0.71 - * @access private * @var array */ var $last_query; @@ -129,7 +120,6 @@ * Results of the last query made * * @since 0.71 - * @access private * @var array|null */ var $last_result; @@ -138,7 +128,6 @@ * MySQL result, which is either a resource or boolean. * * @since 0.71 - * @access protected * @var mixed */ protected $result; @@ -147,7 +136,6 @@ * Cached column info, for sanity checking data before inserting * * @since 4.2.0 - * @access protected * @var array */ protected $col_meta = array(); @@ -156,7 +144,6 @@ * Calculated character sets on tables * * @since 4.2.0 - * @access protected * @var array */ protected $table_charset = array(); @@ -165,7 +152,6 @@ * Whether text fields in the current query need to be sanity checked. * * @since 4.2.0 - * @access protected * @var bool */ protected $check_current_query = true; @@ -174,9 +160,8 @@ * Flag to ensure we don't run into recursion problems when checking the collation. * * @since 4.2.0 - * @access private * @see wpdb::check_safe_collation() - * @var boolean + * @var bool */ private $checking_collation = false; @@ -184,7 +169,6 @@ * Saved info on the table column * * @since 0.71 - * @access protected * @var array */ protected $col_info; @@ -193,7 +177,6 @@ * Saved queries that were executed * * @since 1.5.0 - * @access private * @var array */ var $queries; @@ -202,7 +185,6 @@ * The number of times to retry reconnecting before dying. * * @since 3.9.0 - * @access protected * @see wpdb::check_connection() * @var int */ @@ -216,16 +198,14 @@ * security precautions. * * @since 2.5.0 - * @access private * @var string */ - var $prefix = ''; + public $prefix = ''; /** * WordPress base table prefix. * * @since 3.0.0 - * @access public * @var string */ public $base_prefix; @@ -234,7 +214,6 @@ * Whether the database queries are ready to start executing. * * @since 2.3.2 - * @access private * @var bool */ var $ready = false; @@ -243,7 +222,6 @@ * Blog ID. * * @since 3.0.0 - * @access public * @var int */ public $blogid = 0; @@ -252,7 +230,6 @@ * Site ID. * * @since 3.0.0 - * @access public * @var int */ public $siteid = 0; @@ -261,12 +238,11 @@ * List of WordPress per-blog tables * * @since 2.5.0 - * @access private * @see wpdb::tables() * @var array */ var $tables = array( 'posts', 'comments', 'links', 'options', 'postmeta', - 'terms', 'term_taxonomy', 'term_relationships', 'commentmeta' ); + 'terms', 'term_taxonomy', 'term_relationships', 'termmeta', 'commentmeta' ); /** * List of deprecated WordPress tables @@ -274,7 +250,6 @@ * categories, post2cat, and link2cat were deprecated in 2.3.0, db version 5539 * * @since 2.9.0 - * @access private * @see wpdb::tables() * @var array */ @@ -284,7 +259,6 @@ * List of WordPress global tables * * @since 3.0.0 - * @access private * @see wpdb::tables() * @var array */ @@ -294,7 +268,6 @@ * List of Multisite global tables * * @since 3.0.0 - * @access private * @see wpdb::tables() * @var array */ @@ -305,7 +278,6 @@ * WordPress Comments table * * @since 1.5.0 - * @access public * @var string */ public $comments; @@ -314,7 +286,6 @@ * WordPress Comment Metadata table * * @since 2.9.0 - * @access public * @var string */ public $commentmeta; @@ -323,7 +294,6 @@ * WordPress Links table * * @since 1.5.0 - * @access public * @var string */ public $links; @@ -332,7 +302,6 @@ * WordPress Options table * * @since 1.5.0 - * @access public * @var string */ public $options; @@ -341,7 +310,6 @@ * WordPress Post Metadata table * * @since 1.5.0 - * @access public * @var string */ public $postmeta; @@ -350,7 +318,6 @@ * WordPress Posts table * * @since 1.5.0 - * @access public * @var string */ public $posts; @@ -359,7 +326,6 @@ * WordPress Terms table * * @since 2.3.0 - * @access public * @var string */ public $terms; @@ -368,7 +334,6 @@ * WordPress Term Relationships table * * @since 2.3.0 - * @access public * @var string */ public $term_relationships; @@ -377,20 +342,26 @@ * WordPress Term Taxonomy table * * @since 2.3.0 - * @access public * @var string */ public $term_taxonomy; - /* - * Global and Multisite tables + /** + * WordPress Term Meta table. + * + * @since 4.4.0 + * @var string */ + public $termmeta; + + // + // Global and Multisite tables + // /** * WordPress User Metadata table * * @since 2.3.0 - * @access public * @var string */ public $usermeta; @@ -399,7 +370,6 @@ * WordPress Users table * * @since 1.5.0 - * @access public * @var string */ public $users; @@ -408,7 +378,6 @@ * Multisite Blogs table * * @since 3.0.0 - * @access public * @var string */ public $blogs; @@ -417,7 +386,6 @@ * Multisite Blog Versions table * * @since 3.0.0 - * @access public * @var string */ public $blog_versions; @@ -426,7 +394,6 @@ * Multisite Registration Log table * * @since 3.0.0 - * @access public * @var string */ public $registration_log; @@ -435,7 +402,6 @@ * Multisite Signups table * * @since 3.0.0 - * @access public * @var string */ public $signups; @@ -444,7 +410,6 @@ * Multisite Sites table * * @since 3.0.0 - * @access public * @var string */ public $site; @@ -453,7 +418,6 @@ * Multisite Sitewide Terms table * * @since 3.0.0 - * @access public * @var string */ public $sitecategories; @@ -462,7 +426,6 @@ * Multisite Site Metadata table * * @since 3.0.0 - * @access public * @var string */ public $sitemeta; @@ -478,7 +441,6 @@ * @see wpdb::update() * @see wpdb::delete() * @see wp_set_wpdb_vars() - * @access public * @var array */ public $field_types = array(); @@ -487,7 +449,6 @@ * Database table columns charset * * @since 2.2.0 - * @access public * @var string */ public $charset; @@ -496,7 +457,6 @@ * Database table columns collate * * @since 2.2.0 - * @access public * @var string */ public $collate; @@ -505,7 +465,6 @@ * Database Username * * @since 2.9.0 - * @access protected * @var string */ protected $dbuser; @@ -514,7 +473,6 @@ * Database Password * * @since 3.1.0 - * @access protected * @var string */ protected $dbpassword; @@ -523,7 +481,6 @@ * Database Name * * @since 3.1.0 - * @access protected * @var string */ protected $dbname; @@ -532,7 +489,6 @@ * Database Host * * @since 3.1.0 - * @access protected * @var string */ protected $dbhost; @@ -541,7 +497,6 @@ * Database Handle * * @since 0.71 - * @access protected * @var string */ protected $dbh; @@ -550,7 +505,6 @@ * A textual description of the last query/get_row/get_var call * * @since 3.0.0 - * @access public * @var string */ public $func_call; @@ -564,7 +518,6 @@ * will force the checks to occur. * * @since 3.3.0 - * @access public * @var bool */ public $is_mysql = null; @@ -573,7 +526,6 @@ * A list of incompatible SQL modes. * * @since 3.9.0 - * @access protected * @var array */ protected $incompatible_modes = array( 'NO_ZERO_DATE', 'ONLY_FULL_GROUP_BY', @@ -583,7 +535,6 @@ * Whether to use mysqli over mysql. * * @since 3.9.0 - * @access private * @var bool */ private $use_mysqli = false; @@ -592,7 +543,6 @@ * Whether we've managed to successfully connect at some point * * @since 3.9.0 - * @access private * @var bool */ private $has_connected = false; @@ -607,10 +557,12 @@ * @link https://core.trac.wordpress.org/ticket/3354 * @since 2.0.8 * - * @param string $dbuser MySQL database user + * @global string $wp_version + * + * @param string $dbuser MySQL database user * @param string $dbpassword MySQL database password - * @param string $dbname MySQL database name - * @param string $dbhost MySQL database host + * @param string $dbname MySQL database name + * @param string $dbhost MySQL database host */ public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) { register_shutdown_function( array( $this, '__destruct' ) ); @@ -618,19 +570,12 @@ if ( WP_DEBUG && WP_DEBUG_DISPLAY ) $this->show_errors(); - /* Use ext/mysqli if it exists and: - * - WP_USE_EXT_MYSQL is defined as false, or - * - We are a development version of WordPress, or - * - We are running PHP 5.5 or greater, or - * - ext/mysql is not loaded. - */ + // Use ext/mysqli if it exists unless WP_USE_EXT_MYSQL is defined as true if ( function_exists( 'mysqli_connect' ) ) { + $this->use_mysqli = true; + if ( defined( 'WP_USE_EXT_MYSQL' ) ) { $this->use_mysqli = ! WP_USE_EXT_MYSQL; - } elseif ( version_compare( phpversion(), '5.5', '>=' ) || ! function_exists( 'mysql_connect' ) ) { - $this->use_mysqli = true; - } elseif ( false !== strpos( $GLOBALS['wp_version'], '-' ) ) { - $this->use_mysqli = true; } } @@ -652,14 +597,14 @@ * * @see wpdb::__construct() * @since 2.0.8 - * @return bool true + * @return true */ public function __destruct() { return true; } /** - * PHP5 style magic getter, used to lazy-load expensive data. + * Makes private properties readable for backward compatibility. * * @since 3.5.0 * @@ -674,7 +619,7 @@ } /** - * Magic function, for backwards compatibility. + * Makes private properties settable for backward compatibility. * * @since 3.5.0 * @@ -694,7 +639,7 @@ } /** - * Magic function, for backwards compatibility. + * Makes private properties check-able for backward compatibility. * * @since 3.5.0 * @@ -707,7 +652,7 @@ } /** - * Magic function, for backwards compatibility. + * Makes private properties un-settable for backward compatibility. * * @since 3.5.0 * @@ -723,33 +668,70 @@ * @since 3.1.0 */ public function init_charset() { + $charset = ''; + $collate = ''; + if ( function_exists('is_multisite') && is_multisite() ) { - $this->charset = 'utf8'; + $charset = 'utf8'; if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) { - $this->collate = DB_COLLATE; + $collate = DB_COLLATE; } else { - $this->collate = 'utf8_general_ci'; + $collate = 'utf8_general_ci'; } } elseif ( defined( 'DB_COLLATE' ) ) { - $this->collate = DB_COLLATE; + $collate = DB_COLLATE; } if ( defined( 'DB_CHARSET' ) ) { - $this->charset = DB_CHARSET; + $charset = DB_CHARSET; } - if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) ) - || ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) ) { - return; + $charset_collate = $this->determine_charset( $charset, $collate ); + + $this->charset = $charset_collate['charset']; + $this->collate = $charset_collate['collate']; + } + + /** + * Determines the best charset and collation to use given a charset and collation. + * + * For example, when able, utf8mb4 should be used instead of utf8. + * + * @since 4.6.0 + * + * @param string $charset The character set to check. + * @param string $collate The collation to check. + * @return array The most appropriate character set and collation to use. + */ + public function determine_charset( $charset, $collate ) { + if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) { + return compact( 'charset', 'collate' ); } - if ( 'utf8' === $this->charset && $this->has_cap( 'utf8mb4' ) ) { - $this->charset = 'utf8mb4'; + if ( 'utf8' === $charset && $this->has_cap( 'utf8mb4' ) ) { + $charset = 'utf8mb4'; + } + + if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) { + $charset = 'utf8'; + $collate = str_replace( 'utf8mb4_', 'utf8_', $collate ); } - if ( 'utf8mb4' === $this->charset && ( ! $this->collate || stripos( $this->collate, 'utf8_' ) === 0 ) ) { - $this->collate = 'utf8mb4_unicode_ci'; + if ( 'utf8mb4' === $charset ) { + // _general_ is outdated, so we can upgrade it to _unicode_, instead. + if ( ! $collate || 'utf8_general_ci' === $collate ) { + $collate = 'utf8mb4_unicode_ci'; + } else { + $collate = str_replace( 'utf8_', 'utf8mb4_', $collate ); + } } + + // _unicode_520_ is a better collation, we should use that when it's available. + if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) { + $collate = 'utf8mb4_unicode_520_ci'; + } + + return compact( 'charset', 'collate' ); } /** @@ -767,10 +749,14 @@ if ( ! isset( $collate ) ) $collate = $this->collate; if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) { + $set_charset_succeeded = true; + if ( $this->use_mysqli ) { if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) { - mysqli_set_charset( $dbh, $charset ); - } else { + $set_charset_succeeded = mysqli_set_charset( $dbh, $charset ); + } + + if ( $set_charset_succeeded ) { $query = $this->prepare( 'SET NAMES %s', $charset ); if ( ! empty( $collate ) ) $query .= $this->prepare( ' COLLATE %s', $collate ); @@ -778,8 +764,9 @@ } } else { if ( function_exists( 'mysql_set_charset' ) && $this->has_cap( 'set_charset' ) ) { - mysql_set_charset( $charset, $dbh ); - } else { + $set_charset_succeeded = mysql_set_charset( $charset, $dbh ); + } + if ( $set_charset_succeeded ) { $query = $this->prepare( 'SET NAMES %s', $charset ); if ( ! empty( $collate ) ) $query .= $this->prepare( ' COLLATE %s', $collate ); @@ -831,7 +818,7 @@ $modes = array_change_key_case( $modes, CASE_UPPER ); /** - * Filter the list of incompatible SQL modes to exclude. + * Filters the list of incompatible SQL modes to exclude. * * @since 3.9.0 * @@ -839,7 +826,7 @@ */ $incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes ); - foreach( $modes as $i => $mode ) { + foreach ( $modes as $i => $mode ) { if ( in_array( $mode, $incompatible_modes ) ) { unset( $modes[ $i ] ); } @@ -859,8 +846,8 @@ * * @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. + * @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 */ public function set_prefix( $prefix, $set_table_names = true ) { @@ -897,14 +884,15 @@ * Sets blog id. * * @since 3.0.0 - * @access public + * * @param int $blog_id - * @param int $site_id Optional. + * @param int $network_id Optional. * @return int previous blog id */ - public function set_blog_id( $blog_id, $site_id = 0 ) { - if ( ! empty( $site_id ) ) - $this->siteid = $site_id; + public function set_blog_id( $blog_id, $network_id = 0 ) { + if ( ! empty( $network_id ) ) { + $this->siteid = $network_id; + } $old_blog_id = $this->blogid; $this->blogid = $blog_id; @@ -962,10 +950,10 @@ * @uses wpdb::$global_tables * @uses wpdb::$ms_global_tables * - * @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. + * @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. */ public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) { @@ -1025,33 +1013,57 @@ * * @since 0.71 * - * @param string $db MySQL database name - * @param resource $dbh Optional link identifier. - * @return null Always null. + * @param string $db MySQL database name + * @param resource|null $dbh Optional link identifier. */ public function select( $db, $dbh = null ) { if ( is_null($dbh) ) $dbh = $this->dbh; if ( $this->use_mysqli ) { - $success = @mysqli_select_db( $dbh, $db ); + $success = mysqli_select_db( $dbh, $db ); } else { - $success = @mysql_select_db( $db, $dbh ); + $success = mysql_select_db( $db, $dbh ); } if ( ! $success ) { $this->ready = false; if ( ! did_action( 'template_redirect' ) ) { wp_load_translations_early(); - $this->bail( sprintf( __( '
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.
%2$s
have permission to use the %1$s
database?username_%1$s
. Could that be the problem?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' ); + + $message = '' . sprintf(
+ /* translators: %s: database name */
+ __( 'We were able to connect to the database server (which means your username and password is okay) but not able to select the %s database.' ),
+ '' . htmlspecialchars( $db, ENT_QUOTES ) . '
'
+ ) . "
' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '
',
+ '' . htmlspecialchars( $db, ENT_QUOTES ) . '
'
+ ) . "username_%1$s
. Could that be the problem?' ),
+ htmlspecialchars( $db, ENT_QUOTES )
+ ). "' . sprintf( + /* translators: %s: support forums URL */ + __( '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.' ), + __( 'https://wordpress.org/support/' ) + ) . "
\n"; + + $this->bail( $message, 'db_select_fail' ); } - return; } } @@ -1061,17 +1073,16 @@ * Use esc_sql() or wpdb::prepare() instead. * * @since 2.8.0 - * @deprecated 3.6.0 + * @deprecated 3.6.0 Use wpdb::prepare() * @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()' ); + _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); return addslashes( $string ); } @@ -1081,7 +1092,6 @@ * @see mysqli_real_escape_string() * @see mysql_real_escape_string() * @since 2.8.0 - * @access private * * @param string $string to escape * @return string escaped @@ -1089,19 +1099,22 @@ function _real_escape( $string ) { if ( $this->dbh ) { if ( $this->use_mysqli ) { - return mysqli_real_escape_string( $this->dbh, $string ); + $escaped = mysqli_real_escape_string( $this->dbh, $string ); } else { - return mysql_real_escape_string( $string, $this->dbh ); + $escaped = mysql_real_escape_string( $string, $this->dbh ); } + } else { + $class = get_class( $this ); + if ( function_exists( '__' ) ) { + /* translators: %s: database access abstraction class, usually wpdb or a class extending wpdb */ + _doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' ); + } else { + _doing_it_wrong( $class, sprintf( '%s must set a database connection for use with escaping.', $class ), '3.6.0' ); + } + $escaped = addslashes( $string ); } - $class = get_class( $this ); - if ( function_exists( '__' ) ) { - _doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), E_USER_NOTICE ); - } else { - _doing_it_wrong( $class, sprintf( '%s must set a database connection for use with escaping.', $class ), E_USER_NOTICE ); - } - return addslashes( $string ); + return $this->add_placeholder_escape( $escaped ); } /** @@ -1109,18 +1122,18 @@ * * @uses wpdb::_real_escape() * @since 2.8.0 - * @access private * * @param string|array $data * @return string|array escaped */ - function _escape( $data ) { + public function _escape( $data ) { if ( is_array( $data ) ) { foreach ( $data as $k => $v ) { - if ( is_array($v) ) + if ( is_array( $v ) ) { $data[$k] = $this->_escape( $v ); - else + } else { $data[$k] = $this->_real_escape( $v ); + } } } else { $data = $this->_real_escape( $data ); @@ -1135,7 +1148,7 @@ * Use esc_sql() or wpdb::prepare() instead. * * @since 0.71 - * @deprecated 3.6.0 + * @deprecated 3.6.0 Use wpdb::prepare() * @see wpdb::prepare() * @see esc_sql() * @@ -1144,7 +1157,7 @@ */ public function escape( $data ) { if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) - _deprecated_function( __METHOD__, '3.6', 'wpdb::prepare() or esc_sql()' ); + _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); if ( is_array( $data ) ) { foreach ( $data as $k => $v ) { if ( is_array( $v ) ) @@ -1163,9 +1176,10 @@ * 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 */ public function escape_by_ref( &$string ) { if ( ! is_float( $string ) ) @@ -1175,58 +1189,120 @@ /** * Prepares a SQL query for safe execution. Uses sprintf()-like syntax. * - * The following directives can be used in the query format string: + * The following placeholders can be used in the query string: * %d (integer) * %f (float) * %s (string) - * %% (literal percentage sign - no argument needed) + * + * All placeholders MUST be left unquoted in the query string. A corresponding argument MUST be passed for each placeholder. * - * 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. + * For compatibility with old behavior, numbered or formatted string placeholders (eg, %1$s, %5s) will not have quotes + * added by this function, so should be passed with appropriate quotes around them for your usage. * - * 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. + * Literal percentage signs (%) in the query string must be written as %%. Percentage wildcards (for example, + * to use in LIKE syntax) must be passed via a substitution argument containing the complete LIKE string, these + * cannot be inserted directly in the query string. Also see {@see esc_like()}. * - * 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' ); + * Arguments may be passed as individual arguments to the method, or as a single array containing all arguments. A combination + * of the two is not supported. * - * @link http://php.net/sprintf Description of syntax. + * Examples: + * $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", array( 'foo', 1337, '%bar' ) ); + * $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' ); + * + * @link https://secure.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 + * @param string $query Query statement with sprintf()-like placeholders + * @param array|mixed $args The array of variables to substitute into the query's placeholders if being called with an array of arguments, + * or the first variable to substitute into the query's placeholders if being called with individual arguments. + * @param mixed $args,... further variables to substitute into the query's placeholders if being called wih individual arguments. + * @return string|void Sanitized query string, if there is a query to prepare. */ public function prepare( $query, $args ) { - if ( is_null( $query ) ) + if ( is_null( $query ) ) { return; + } // This is not meant to be foolproof -- but it will catch obviously incorrect usage. if ( strpos( $query, '%' ) === false ) { - _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9' ); + wp_load_translations_early(); + _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' ); } $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]) ) + + // If args were passed as an array (as in vsprintf), move them up. + $passed_as_array = false; + if ( is_array( $args[0] ) && count( $args ) == 1 ) { + $passed_as_array = true; $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( '|(?add_placeholder_escape( $query ); } /** @@ -1235,16 +1311,17 @@ * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security. * * Example Prepared Statement: - * $wild = '%'; - * $find = 'only 43% of planets'; - * $like = $wild . $wpdb->esc_like( $find ) . $wild; - * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like ); + * + * $wild = '%'; + * $find = 'only 43% of planets'; + * $like = $wild . $wpdb->esc_like( $find ) . $wild; + * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like ); * * Example Escape Chain: - * $sql = esc_sql( $wpdb->esc_like( $input ) ); + * + * $sql = esc_sql( $wpdb->esc_like( $input ) ); * * @since 4.0.0 - * @access public * * @param string $text The raw text to be escaped. The input typed by the user should have no * extra or deleted slashes. @@ -1262,7 +1339,7 @@ * @global array $EZSQL_ERROR Stores error information of query and error string * * @param string $str The error to display - * @return false|null False if the showing of errors is disabled. + * @return false|void False if the showing of errors is disabled. */ public function print_error( $str = '' ) { global $EZSQL_ERROR; @@ -1281,10 +1358,13 @@ wp_load_translations_early(); - if ( $caller = $this->get_caller() ) + if ( $caller = $this->get_caller() ) { + /* translators: 1: Database error message, 2: SQL query, 3: Name of the calling function */ $error_str = sprintf( __( 'WordPress database error %1$s for query %2$s made by %3$s' ), $str, $this->last_query, $caller ); - else + } else { + /* translators: 1: Database error message, 2: SQL query */ $error_str = sprintf( __( 'WordPress database error %1$s for query %2$s' ), $str, $this->last_query ); + } error_log( $error_str ); @@ -1294,19 +1374,29 @@ // 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' ) ) + $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' ) ) + } + 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
%s [%s]%s
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' ); + $message = '' . 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.' ),
+ 'wp-config.php
',
+ '' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '
'
+ ) . "
' . 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 WordPress Support Forums.' ), + __( 'https://wordpress.org/support/' ) + ) . "
\n"; + + $this->bail( $message, 'db_connect_fail' ); return false; } elseif ( $this->dbh ) { @@ -1518,10 +1618,64 @@ } /** - * Check that the connection to the database is still up. If not, try to reconnect. + * Parse the DB_HOST setting to interpret it for mysqli_real_connect. + * + * mysqli_real_connect doesn't support the host param including a port or + * socket like mysql_connect does. This duplicates how mysql_connect detects + * a port and/or socket file. + * + * @since 4.9.0 + * + * @param string $host The DB_HOST setting to parse. + * @return array|bool Array containing the host, the port, the socket and whether + * it is an IPv6 address, in that order. If $host couldn't be parsed, + * returns false. + */ + public function parse_db_host( $host ) { + $port = null; + $socket = null; + $is_ipv6 = false; + + // First peel off the socket parameter from the right, if it exists. + $socket_pos = strpos( $host, ':/' ); + if ( $socket_pos !== false ) { + $socket = substr( $host, $socket_pos + 1 ); + $host = substr( $host, 0, $socket_pos ); + } + + // We need to check for an IPv6 address first. + // An IPv6 address will always contain at least two colons. + if ( substr_count( $host, ':' ) > 1 ) { + $pattern = '#^(?:\[)?(?P' . 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.' ),
+ '' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '
'
+ ) . "
' . 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 WordPress Support Forums.' ), + __( 'https://wordpress.org/support/' ) + ) . "
\n"; + // We weren't able to reconnect, so we better bail. - $this->bail( sprintf( ( " -This means that we lost contact with 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' ); + $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(); @@ -1610,7 +1777,7 @@ } /** - * Filter the database query. + * Filters the database query. * * Some queries are made before the plugins have been loaded, * and thus cannot be filtered with this method. @@ -1640,18 +1807,28 @@ $this->check_current_query = true; - // Keep track of the last query for debug.. + // 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 server has gone away, try to reconnect. $mysql_errno = 0; if ( ! empty( $this->dbh ) ) { if ( $this->use_mysqli ) { - $mysql_errno = mysqli_errno( $this->dbh ); + 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 { - $mysql_errno = mysql_errno( $this->dbh ); + if ( is_resource( $this->dbh ) ) { + $mysql_errno = mysql_errno( $this->dbh ); + } else { + $mysql_errno = 2006; + } } } @@ -1664,11 +1841,19 @@ } } - // If there is an error then take note of it.. + // If there is an error then take note of it. if ( $this->use_mysqli ) { - $this->last_error = mysqli_error( $this->dbh ); + 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 { - $this->last_error = mysql_error( $this->dbh ); + 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 ) { @@ -1701,12 +1886,12 @@ } else { $num_rows = 0; if ( $this->use_mysqli && $this->result instanceof mysqli_result ) { - while ( $row = @mysqli_fetch_object( $this->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 ) ) { + while ( $row = mysql_fetch_object( $this->result ) ) { $this->last_result[$num_rows] = $row; $num_rows++; } @@ -1726,7 +1911,6 @@ * * @since 3.9.0 * - * @access private * @see wpdb::query() * * @param string $query The query to run. @@ -1736,10 +1920,10 @@ $this->timer_start(); } - if ( $this->use_mysqli ) { - $this->result = @mysqli_query( $this->dbh, $query ); - } else { - $this->result = @mysql_query( $query, $this->dbh ); + 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++; @@ -1749,6 +1933,64 @@ } /** + * Generates and returns a placeholder escape string for use in queries returned by ::prepare(). + * + * @since 4.8.3 + * + * @return string String to escape placeholders. + */ + public function placeholder_escape() { + static $placeholder; + + if ( ! $placeholder ) { + // If ext/hash is not present, compat.php's hash_hmac() does not support sha256. + $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1'; + // Old WP installs may not have AUTH_SALT defined. + $salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand(); + + $placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}'; + } + + /* + * Add the filter to remove the placeholder escaper. Uses priority 0, so that anything + * else attached to this filter will recieve the query with the placeholder string removed. + */ + if ( ! has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) { + add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 ); + } + + return $placeholder; + } + + /** + * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder. + * + * @since 4.8.3 + * + * @param string $query The query to escape. + * @return string The query with the placeholder escape string inserted where necessary. + */ + public function add_placeholder_escape( $query ) { + /* + * To prevent returning anything that even vaguely resembles a placeholder, + * we clobber every % we can find. + */ + return str_replace( '%', $this->placeholder_escape(), $query ); + } + + /** + * Removes the placeholder escape strings from a query. + * + * @since 4.8.3 + * + * @param string $query The query from which the placeholder will be removed. + * @return string The query with the placeholder removed. + */ + public function remove_placeholder_escape( $query ) { + return str_replace( $this->placeholder_escape(), '%', $query ); + } + + /** * Insert a row into a table. * * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) ) @@ -1759,10 +2001,14 @@ * @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 $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 ) { @@ -1780,10 +2026,14 @@ * @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 $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 ) { @@ -1795,17 +2045,20 @@ * * 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. + * @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' ) { @@ -1822,6 +2075,11 @@ $formats = $values = array(); foreach ( $data as $value ) { + if ( is_null( $value['value'] ) ) { + $formats[] = 'NULL'; + continue; + } + $formats[] = $value['format']; $values[] = $value['value']; } @@ -1846,12 +2104,23 @@ * @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. + * @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 ) { @@ -1870,10 +2139,20 @@ $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']; } @@ -1898,9 +2177,15 @@ * @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. + * @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 ) { @@ -1915,6 +2200,11 @@ $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']; } @@ -1937,12 +2227,11 @@ * 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 + * @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 ) { @@ -1974,7 +2263,6 @@ * 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. @@ -2010,17 +2298,18 @@ * 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 The same array as $data with additional 'charset' keys. + * @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 it's sprintf() could take more. + /* + * 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 ); @@ -2039,18 +2328,19 @@ * 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 + * @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. + /* + * 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 ); @@ -2075,14 +2365,14 @@ * @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. + * @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_safe_collation( $query ) ) { + if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } @@ -2106,16 +2396,16 @@ * * @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 + * @param string|null $query SQL query. + * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to + * an stdClass object, an associative array, or a numeric array, respectively. Default OBJECT. + * @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_safe_collation( $query ) ) { + if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } @@ -2152,11 +2442,11 @@ * @since 0.71 * * @param string|null $query Optional. SQL query. Defaults to previous query. - * @param int $x Optional. Column to return. Indexed from 0. + * @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_safe_collation( $query ) ) { + if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } @@ -2179,16 +2469,18 @@ * * @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 + * @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_safe_collation( $query ) ) { + if ( $this->check_current_query && $this->check_safe_collation( $query ) ) { $this->check_current_query = false; } @@ -2215,7 +2507,7 @@ } elseif ( $output == ARRAY_A || $output == ARRAY_N ) { // Return an integer-keyed array of... if ( $this->last_result ) { - foreach( (array) $this->last_result as $row ) { + foreach ( (array) $this->last_result as $row ) { if ( $output == ARRAY_N ) { // ...integer-keyed row arrays $new_array[] = array_values( get_object_vars( $row ) ); @@ -2237,7 +2529,6 @@ * 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. @@ -2246,7 +2537,7 @@ $tablekey = strtolower( $table ); /** - * Filter the table charset value before the DB is checked. + * 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. @@ -2338,19 +2629,18 @@ * 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. WP_Error object if there was an error. + * @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 ); /** - * Filter the column charset value before the DB is checked. + * 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. @@ -2367,7 +2657,7 @@ } // Skip this entirely if this isn't a MySQL database. - if ( false === $this->is_mysql ) { + if ( empty( $this->is_mysql ) ) { return false; } @@ -2403,20 +2693,19 @@ * 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. + * @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 ( false === $this->is_mysql ) { + if ( empty( $this->is_mysql ) ) { return false; } @@ -2448,47 +2737,45 @@ '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; } /** @@ -2498,7 +2785,6 @@ * 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. @@ -2519,7 +2805,6 @@ * 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. @@ -2560,7 +2845,7 @@ } // If any of the columns don't have one of these collations, it needs more sanity checking. - foreach( $this->col_meta[ $table ] as $col ) { + foreach ( $this->col_meta[ $table ] as $col ) { if ( empty( $col->Collation ) ) { continue; } @@ -2577,7 +2862,6 @@ * 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 @@ -2595,8 +2879,13 @@ if ( is_array( $value['length'] ) ) { $length = $value['length']['length']; + $truncate_by_byte_length = 'byte' === $value['length']['type']; } else { $length = false; + // Since we have no length, we'll never truncate. + // Initialize the variable to false. true would take us + // through an unnecessary (for this case) codepath below. + $truncate_by_byte_length = false; } // There's no charset to work with. @@ -2609,8 +2898,6 @@ continue; } - $truncate_by_byte_length = 'byte' === $value['length']['type']; - $needs_validation = true; if ( // latin1 can store any byte sequence @@ -2676,56 +2963,56 @@ $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'] ); + // Using binary causes LEFT() to truncate by bytes. + $charset = 'binary'; + } else { + $charset = $value['charset']; + } + + if ( $this->charset ) { + $connection_charset = $this->charset; } else { - $queries[ $value['charset'] ][ $col ] = $this->prepare( "LEFT( CONVERT( %s USING {$value['charset']} ), %d )", $value['value'], $value['length']['length'] ); + if ( $this->use_mysqli ) { + $connection_charset = mysqli_character_set_name( $this->dbh ); + } else { + $connection_charset = mysql_client_encoding(); + } + } + + if ( is_array( $value['length'] ) ) { + $length = sprintf( '%.0f', $value['length']['length'] ); + $queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] ); + } else if ( 'binary' !== $charset ) { + // If we don't have a length, there's no need to convert binary - it will always return the same result. + $queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] ); } unset( $data[ $col ]['db'] ); } } - $connection_charset = $this->charset; - foreach ( $queries as $charset => $query ) { + $sql = array(); + foreach ( $queries as $column => $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 ) { + $sql[] = $query . " AS x_$column"; + } + + $this->check_current_query = false; + $row = $this->get_row( "SELECT " . implode( ', ', $sql ), ARRAY_A ); + if ( ! $row ) { + return new WP_Error( 'wpdb_strip_invalid_text_failure' ); + } + + foreach ( array_keys( $data ) as $column ) { + if ( isset( $row["x_$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; @@ -2735,7 +3022,6 @@ * 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 WP_Error object if the conversion fails. @@ -2781,7 +3067,6 @@ * 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. @@ -2822,7 +3107,6 @@ * 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. @@ -2834,11 +3118,8 @@ // 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 ) ); + // Strip everything between parentheses except nested selects. + $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query ); // Quickly match most common queries. if ( preg_match( '/^\s*(?:' @@ -2846,17 +3127,23 @@ . '|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 ) ) { + . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?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] ); + // SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts' + if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) { + return $maybe[2]; + } + + // SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%' + // This quoted LIKE operand seldom holds a full table name. + // It is usually a pattern for matching a prefix so we just + // strip the trailing % and unescape the _ to get 'wp_123_' + // which drop-ins can use for routing these SQL statements. + if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) { + return str_replace( '\\_', '_', $maybe[2] ); } // Big pattern for the rest of the table-related queries. @@ -2874,7 +3161,7 @@ . '|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 ) ) { + . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) { return str_replace( '`', '', $maybe[1] ); } @@ -2886,19 +3173,20 @@ * * @since 3.5.0 * - * @access protected */ protected function load_col_info() { if ( $this->col_info ) return; if ( $this->use_mysqli ) { - for ( $i = 0; $i < @mysqli_num_fields( $this->result ); $i++ ) { - $this->col_info[ $i ] = @mysqli_fetch_field( $this->result ); + $num_fields = mysqli_num_fields( $this->result ); + for ( $i = 0; $i < $num_fields; $i++ ) { + $this->col_info[ $i ] = mysqli_fetch_field( $this->result ); } } else { - for ( $i = 0; $i < @mysql_num_fields( $this->result ); $i++ ) { - $this->col_info[ $i ] = @mysql_fetch_field( $this->result, $i ); + $num_fields = mysql_num_fields( $this->result ); + for ( $i = 0; $i < $num_fields; $i++ ) { + $this->col_info[ $i ] = mysql_fetch_field( $this->result, $i ); } } } @@ -2908,8 +3196,8 @@ * * @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 + * @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 */ public function get_col_info( $info_type = 'name', $col_offset = -1 ) { @@ -2919,7 +3207,7 @@ if ( $col_offset == -1 ) { $i = 0; $new_array = array(); - foreach( (array) $this->col_info as $col ) { + foreach ( (array) $this->col_info as $col ) { $new_array[$i] = $col->{$info_type}; $i++; } @@ -2935,7 +3223,7 @@ * * @since 1.5.0 * - * @return bool + * @return true */ public function timer_start() { $this->time_start = microtime( true ); @@ -2960,35 +3248,68 @@ * * @since 1.5.0 * - * @param string $message The Error message + * @param string $message The Error message * @param string $error_code Optional. A Computer readable string to identify the error. * @return false|void */ public function bail( $message, $error_code = '500' ) { if ( !$this->show_errors ) { - if ( class_exists( 'WP_Error' ) ) + if ( class_exists( 'WP_Error', false ) ) { $this->error = new WP_Error($error_code, $message); - else + } else { $this->error = $message; + } return false; } wp_die($message); } + + /** + * Closes the current database connection. + * + * @since 4.5.0 + * + * @return bool True if the connection was successfully closed, false if it wasn't, + * or the connection doesn't exist. + */ + public function close() { + if ( ! $this->dbh ) { + return false; + } + + if ( $this->use_mysqli ) { + $closed = mysqli_close( $this->dbh ); + } else { + $closed = mysql_close( $this->dbh ); + } + + if ( $closed ) { + $this->dbh = null; + $this->ready = false; + $this->has_connected = false; + } + + return $closed; + } + /** * Whether MySQL database is at least the required minimum version. * * @since 2.5.0 - * @uses $wp_version - * @uses $required_mysql_version * - * @return WP_Error + * @global string $wp_version + * @global string $required_mysql_version + * + * @return WP_Error|void */ public 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, '<') ) + if ( version_compare($this->db_version(), $required_mysql_version, '<') ) { + /* translators: 1: WordPress version number, 2: Minimum required MySQL version number */ return new WP_Error('database_version', sprintf( __( 'ERROR: WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version )); + } } /** @@ -2996,14 +3317,15 @@ * * Called when WordPress is generating the table scheme. * + * Use `wpdb::has_cap( 'collation' )`. + * * @since 2.5.0 - * @deprecated 3.5.0 - * @deprecated Use wpdb::has_cap( 'collation' ) + * @deprecated 3.5.0 Use wpdb::has_cap() * * @return bool True if collation is supported, false if version does not */ public function supports_collation() { - _deprecated_function( __FUNCTION__, '3.5', 'wpdb::has_cap( \'collation\' )' ); + _deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' ); return $this->has_cap( 'collation' ); } @@ -3029,14 +3351,15 @@ * Determine if a database supports a particular feature. * * @since 2.7.0 - * @since 4.1.0 Support was added for the 'utf8mb4' feature. + * @since 4.1.0 Added support for the 'utf8mb4' feature. + * @since 4.6.0 Added support for the 'utf8mb4_520' feature. * * @see wpdb::db_version() * * @param string $db_cap The feature to check for. Accepts 'collation', * 'group_concat', 'subqueries', 'set_charset', - * or 'utf8mb4'. - * @return bool Whether the database feature is supported, false otherwise. + * 'utf8mb4', or 'utf8mb4_520'. + * @return int|false Whether the database feature is supported, false otherwise. */ public function has_cap( $db_cap ) { $version = $this->db_version(); @@ -3068,6 +3391,8 @@ } else { return version_compare( $client_version, '5.5.3', '>=' ); } + case 'utf8mb4_520' : // @since 4.6.0 + return version_compare( $version, '5.6', '>=' ); } return false; @@ -3081,14 +3406,14 @@ * * @since 2.5.0 * - * @return string The name of the calling function + * @return string|array The name of the calling function */ public function get_caller() { return wp_debug_backtrace_summary( __CLASS__ ); } /** - * The database version number. + * Retrieves the MySQL server version. * * @since 2.7.0 *