WordPress 4.2.2
[autoinstalls/wordpress.git] / wp-includes / wp-db.php
index 0bd7a1882bff96055cb13debfc65e45fe301248a..e749f517c43beac5f953c7bb8141c39a920d34c8 100644 (file)
@@ -1809,6 +1809,8 @@ class wpdb {
         * @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;
                }
@@ -1829,7 +1831,6 @@ class wpdb {
 
                $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
 
-               $this->insert_id = 0;
                $this->check_current_query = false;
                return $this->query( $this->prepare( $sql, $values ) );
        }
@@ -2021,17 +2022,11 @@ class wpdb {
                                // 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;
-                       } elseif ( $this->check_ascii( $value['value'] ) ) {
-                               // If it's ASCII, then we don't need the charset. We can skip this field.
-                               $value['charset'] = false;
                        } else {
                                $value['charset'] = $this->get_col_charset( $table, $field );
                                if ( is_wp_error( $value['charset'] ) ) {
                                        return false;
                                }
-
-                               // This isn't ASCII. Don't have strip_invalid_text() re-check.
-                               $value['ascii'] = false;
                        }
 
                        $data[ $field ] = $value;
@@ -2064,10 +2059,6 @@ class wpdb {
                                }
                        }
 
-                       if ( false !== $value['length'] && mb_strlen( $value['value'] ) > $value['length'] ) {
-                               return false;
-                       }
-
                        $data[ $field ] = $value;
                }
 
@@ -2275,7 +2266,10 @@ class wpdb {
                }
 
                $charsets = $columns = array();
-               $results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" );
+
+               $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' );
                }
@@ -2406,14 +2400,16 @@ class wpdb {
 
        /**
         * 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 Max column length as an int. False if the column has no
-        *               length. WP_Error object if there was an error.
+        * @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 );
@@ -2446,27 +2442,47 @@ class wpdb {
                }
 
                switch( $type ) {
-                       case 'binary':
                        case 'char':
-                       case 'varbinary':
                        case 'varchar':
-                               return $length;
+                               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 255; // 2^8 - 1
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 255,        // 2^8 - 1
+                               );
                                break;
                        case 'blob':
                        case 'text':
-                               return 65535; // 2^16 - 1
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 65535,      // 2^16 - 1
+                               );
                                break;
                        case 'mediumblob':
                        case 'mediumtext':
-                               return 16777215; // 2^24 - 1
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 16777215,   // 2^24 - 1
+                               );
                                break;
                        case 'longblob':
                        case 'longtext':
-                               return 4294967295; // 2^32 - 1
+                               return array(
+                                       'type'   => 'byte',
+                                       'length' => 4294967295, // 2^32 - 1
+                               );
                                break;
                        default:
                                return false;
@@ -2515,7 +2531,7 @@ class wpdb {
 
                // 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)\s/i', $query ) ) {
+               if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
                        return true;
                }
 
@@ -2572,50 +2588,55 @@ class wpdb {
         *                        remove invalid characters, a WP_Error object is returned.
         */
        protected function strip_invalid_text( $data ) {
-               // Some multibyte character sets that we can check in PHP.
-               $mb_charsets = array(
-                       'ascii'   => 'ASCII',
-                       'big5'    => 'BIG-5',
-                       'eucjpms' => 'eucJP-win',
-                       'gb2312'  => 'EUC-CN',
-                       'ujis'    => 'EUC-JP',
-                       'utf32'   => 'UTF-32',
-               );
-
-               $supported_charsets = array();
-               if ( function_exists( 'mb_list_encodings' ) ) {
-                       $supported_charsets = mb_list_encodings();
-               }
-
                $db_check_string = false;
 
                foreach ( $data as &$value ) {
                        $charset = $value['charset'];
 
-                       // Column isn't a string, or is latin1, which will will happily store anything.
-                       if ( false === $charset || 'latin1' === $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;
                        }
 
-                       // ASCII is always OK.
-                       if ( ! isset( $value['ascii'] ) && $this->check_ascii( $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;
                        }
 
-                       // Convert the text locally.
-                       if ( $supported_charsets ) {
-                               if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
-                                       $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
+                       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 ) {
+                       if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
                                $regex = '/
                                        (
                                                (?: [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
@@ -2625,7 +2646,7 @@ class wpdb {
                                                |   \xED[\x80-\x9F][\x80-\xBF]
                                                |   [\xEE-\xEF][\x80-\xBF]{2}';
 
-                               if ( 'utf8mb4' === $charset) {
+                               if ( 'utf8mb4' === $charset ) {
                                        $regex .= '
                                                |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
                                                |    [\xF1-\xF3][\x80-\xBF]{3}
@@ -2633,11 +2654,16 @@ class wpdb {
                                        ';
                                }
 
-                               $regex .= '){1,50}                          # ...one or more times
+                               $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;
                        }
 
@@ -2654,8 +2680,14 @@ class wpdb {
                                                $queries[ $value['charset'] ] = array();
                                        }
 
-                                       // Split the CONVERT() calls by charset, so we can make sure the connection is right
-                                       $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
+                                       // 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'] );
                                }
                        }
@@ -2674,16 +2706,19 @@ class wpdb {
 
                                $this->check_current_query = false;
 
-                               $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
+                               $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' );
                                }
 
-                               $cols = array_keys( $query );
-                               $col_count = count( $cols );
-                               for ( $ii = 0; $ii < $col_count; $ii++ ) {
-                                       $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
+                               foreach ( array_keys( $query ) as $column ) {
+                                       $data[ $column ]['value'] = $row["x_$column"];
                                }
                        }
 
@@ -2706,6 +2741,12 @@ class wpdb {
         * @return string|WP_Error The converted query, or a 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 );
@@ -2725,6 +2766,7 @@ class wpdb {
                        'value'   => $query,
                        'charset' => $charset,
                        'ascii'   => false,
+                       'length'  => false,
                );
 
                $data = $this->strip_invalid_text( array( $data ) );
@@ -2747,7 +2789,7 @@ class wpdb {
         * @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 ) || $this->check_ascii( $value ) ) {
+               if ( ! is_string( $value ) ) {
                        return $value;
                }
 
@@ -2764,7 +2806,7 @@ class wpdb {
                        $column => array(
                                'value'   => $value,
                                'charset' => $charset,
-                               'ascii'   => false,
+                               'length'  => $this->get_col_length( $table, $column ),
                        )
                );
 
@@ -2805,16 +2847,16 @@ class wpdb {
                                . '|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+`?([\w-]+)`?/is', $query, $maybe ) ) {
-                       return $maybe[1];
+                               . ')\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([\w-]+)\W/is', $query, $maybe ) ) {
-                       return $maybe[1];
+                               . ')\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.
@@ -2832,8 +2874,8 @@ class wpdb {
                                . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
                                . '|(?:GRANT|REVOKE).*ON\s+TABLE'
                                . '|SHOW\s+(?:.*FROM|.*TABLE)'
-                               . ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) {
-                       return $maybe[1];
+                               . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is', $query, $maybe ) ) {
+                       return str_replace( '`', '', $maybe[1] );
                }
 
                return false;