Make WordPress Core

Changeset 32182


Ignore:
Timestamp:
04/20/2015 11:08:17 AM (11 years ago)
Author:
pento
Message:

Merge the query sanity checks from #21212 to the 3.9 branch.

Props pento, nacin, mdawaffe, DrewAPicture.

Location:
branches/3.9
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/3.9/src/wp-admin/includes/post.php

    r28113 r32182  
    178178 */
    179179function edit_post( $post_data = null ) {
     180        global $wpdb;
    180181
    181182        if ( empty($post_data) )
     
    318319        update_post_meta( $post_ID, '_edit_last', get_current_user_id() );
    319320
    320         wp_update_post( $post_data );
     321        $success = wp_update_post( $post_data );
     322        // If the save failed, see if we can sanity check the main fields and try again
     323        if ( ! $success && is_callable( array( $wpdb, 'strip_invalid_text_for_column' ) ) ) {
     324                $fields = array( 'post_title', 'post_content', 'post_excerpt' );
     325
     326                foreach( $fields as $field ) {
     327                        if ( isset( $post_data[ $field ] ) ) {
     328                                $post_data[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->posts, $field, $post_data[ $field ] );
     329                        }
     330                }
     331
     332                wp_update_post( $post_data );
     333        }
    321334
    322335        // Now that we have an ID we can fix any attachment anchor hrefs
  • branches/3.9/src/wp-includes/wp-db.php

    r28022 r32182  
    142142
    143143        /**
     144         * Cached column info, for sanity checking data before inserting
     145         *
     146         * @since 4.2.0
     147         * @access protected
     148         * @var array
     149         */
     150        protected $col_meta = array();
     151
     152        /**
     153         * Calculated character sets on tables
     154         *
     155         * @since 4.2.0
     156         * @access protected
     157         * @var array
     158         */
     159        protected $table_charset = array();
     160
     161        /**
     162         * Whether text fields in the current query need to be sanity checked.
     163         *
     164         * @since 4.2.0
     165         * @access protected
     166         * @var bool
     167         */
     168        protected $check_current_query = true;
     169
     170        /**
     171         * Flag to ensure we don't run into recursion problems when checking the collation.
     172         *
     173         * @since 4.2.0
     174         * @access protected
     175         * @see wpdb::check_safe_collation()
     176         * @var boolean
     177         */
     178        protected $checking_collation = false;
     179
     180        /**
    144181         * Saved info on the table column
    145182         *
     
    640677         */
    641678        function __set( $name, $value ) {
     679                $protected_members = array(
     680                        'col_meta',
     681                        'table_charset',
     682                        'check_current_query',
     683                );
     684                if (  in_array( $name, $protected_members, true ) ) {
     685                        return;
     686                }
    642687                $this->$name = $value;
    643688        }
     
    709754                                        if ( ! empty( $collate ) )
    710755                                                $query .= $this->prepare( ' COLLATE %s', $collate );
    711                                         mysqli_query( $query, $dbh );
     756                                        mysqli_query( $dbh, $query );
    712757                                }
    713758                        } else {
     
    14971542         */
    14981543        function query( $query ) {
    1499                 if ( ! $this->ready )
     1544                if ( ! $this->ready ) {
     1545                        $this->check_current_query = true;
    15001546                        return false;
     1547                }
    15011548
    15021549                /**
     
    15171564                // Log how the function was called
    15181565                $this->func_call = "\$db->query(\"$query\")";
     1566
     1567                // If we're writing to the database, make sure the query will write safely.
     1568                if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
     1569                        $stripped_query = $this->strip_invalid_text_from_query( $query );
     1570                        // strip_invalid_text_from_query() can perform queries, so we need
     1571                        // to flush again, just to make sure everything is clear.
     1572                        $this->flush();
     1573                        if ( $stripped_query !== $query ) {
     1574                                $this->insert_id = 0;
     1575                                return false;
     1576                        }
     1577                }
     1578
     1579                $this->check_current_query = true;
    15191580
    15201581                // Keep track of the last query for debug..
     
    16911752         */
    16921753        function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
    1693                 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) )
     1754                if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
    16941755                        return false;
     1756                }
     1757
     1758                $data = $this->process_fields( $table, $data, $format );
     1759                if ( false === $data ) {
     1760                        return false;
     1761                }
     1762
     1763                $formats = $values = array();
     1764                foreach ( $data as $value ) {
     1765                        $formats[] = $value['format'];
     1766                        $values[]  = $value['value'];
     1767                }
     1768
     1769                $fields  = '`' . implode( '`, `', array_keys( $data ) ) . '`';
     1770                $formats = implode( ', ', $formats );
     1771
     1772                $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
     1773
    16951774                $this->insert_id = 0;
    1696                 $formats = $format = (array) $format;
    1697                 $fields = array_keys( $data );
    1698                 $formatted_fields = array();
    1699                 foreach ( $fields as $field ) {
    1700                         if ( !empty( $format ) )
    1701                                 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0];
    1702                         elseif ( isset( $this->field_types[$field] ) )
    1703                                 $form = $this->field_types[$field];
    1704                         else
    1705                                 $form = '%s';
    1706                         $formatted_fields[] = $form;
    1707                 }
    1708                 $sql = "{$type} INTO `$table` (`" . implode( '`,`', $fields ) . "`) VALUES (" . implode( ",", $formatted_fields ) . ")";
    1709                 return $this->query( $this->prepare( $sql, $data ) );
     1775                $this->check_current_query = false;
     1776                return $this->query( $this->prepare( $sql, $values ) );
    17101777        }
    17111778
     
    17321799         */
    17331800        function update( $table, $data, $where, $format = null, $where_format = null ) {
    1734                 if ( ! is_array( $data ) || ! is_array( $where ) )
     1801                if ( ! is_array( $data ) || ! is_array( $where ) ) {
    17351802                        return false;
    1736 
    1737                 $formats = $format = (array) $format;
    1738                 $bits = $wheres = array();
    1739                 foreach ( (array) array_keys( $data ) as $field ) {
    1740                         if ( !empty( $format ) )
    1741                                 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0];
    1742                         elseif ( isset($this->field_types[$field]) )
    1743                                 $form = $this->field_types[$field];
    1744                         else
    1745                                 $form = '%s';
    1746                         $bits[] = "`$field` = {$form}";
    1747                 }
    1748 
    1749                 $where_formats = $where_format = (array) $where_format;
    1750                 foreach ( (array) array_keys( $where ) as $field ) {
    1751                         if ( !empty( $where_format ) )
    1752                                 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0];
    1753                         elseif ( isset( $this->field_types[$field] ) )
    1754                                 $form = $this->field_types[$field];
    1755                         else
    1756                                 $form = '%s';
    1757                         $wheres[] = "`$field` = {$form}";
    1758                 }
    1759 
    1760                 $sql = "UPDATE `$table` SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres );
    1761                 return $this->query( $this->prepare( $sql, array_merge( array_values( $data ), array_values( $where ) ) ) );
     1803                }
     1804
     1805                $data = $this->process_fields( $table, $data, $format );
     1806                if ( false === $data ) {
     1807                        return false;
     1808                }
     1809                $where = $this->process_fields( $table, $where, $where_format );
     1810                if ( false === $where ) {
     1811                        return false;
     1812                }
     1813
     1814                $fields = $conditions = $values = array();
     1815                foreach ( $data as $field => $value ) {
     1816                        $fields[] = "`$field` = " . $value['format'];
     1817                        $values[] = $value['value'];
     1818                }
     1819                foreach ( $where as $field => $value ) {
     1820                        $conditions[] = "`$field` = " . $value['format'];
     1821                        $values[] = $value['value'];
     1822                }
     1823
     1824                $fields = implode( ', ', $fields );
     1825                $conditions = implode( ' AND ', $conditions );
     1826
     1827                $sql = "UPDATE `$table` SET $fields WHERE $conditions";
     1828
     1829                $this->check_current_query = false;
     1830                return $this->query( $this->prepare( $sql, $values ) );
    17621831        }
    17631832
     
    17811850         */
    17821851        function delete( $table, $where, $where_format = null ) {
    1783                 if ( ! is_array( $where ) )
     1852                if ( ! is_array( $where ) ) {
    17841853                        return false;
    1785 
    1786                 $bits = $wheres = array();
    1787 
    1788                 $where_formats = $where_format = (array) $where_format;
    1789 
    1790                 foreach ( array_keys( $where ) as $field ) {
    1791                         if ( !empty( $where_format ) ) {
    1792                                 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0];
     1854                }
     1855
     1856                $where = $this->process_fields( $table, $where, $where_format );
     1857                if ( false === $where ) {
     1858                        return false;
     1859                }
     1860
     1861                $conditions = $values = array();
     1862                foreach ( $where as $field => $value ) {
     1863                        $conditions[] = "`$field` = " . $value['format'];
     1864                        $values[] = $value['value'];
     1865                }
     1866
     1867                $conditions = implode( ' AND ', $conditions );
     1868
     1869                $sql = "DELETE FROM `$table` WHERE $conditions";
     1870
     1871                $this->check_current_query = false;
     1872                return $this->query( $this->prepare( $sql, $values ) );
     1873        }
     1874
     1875
     1876        /**
     1877         * Processes arrays of field/value pairs and field formats.
     1878         *
     1879         * This is a helper method for wpdb's CRUD methods, which take field/value
     1880         * pairs for inserts, updates, and where clauses. This method first pairs
     1881         * each value with a format. Then it determines the charset of that field,
     1882         * using that to determine if any invalid text would be stripped. If text is
     1883         * stripped, then field processing is rejected and the query fails.
     1884         *
     1885         * @since 4.2.0
     1886         * @access protected
     1887         *
     1888         * @param string $table  Table name.
     1889         * @param array  $data   Field/value pair.
     1890         * @param mixed  $format Format for each field.
     1891         * @return array|bool Returns an array of fields that contain paired values
     1892         *                    and formats. Returns false for invalid values.
     1893         */
     1894        protected function process_fields( $table, $data, $format ) {
     1895                $data = $this->process_field_formats( $data, $format );
     1896                $data = $this->process_field_charsets( $data, $table );
     1897                if ( false === $data ) {
     1898                        return false;
     1899                }
     1900
     1901                $converted_data = $this->strip_invalid_text( $data );
     1902
     1903                if ( $data !== $converted_data ) {
     1904                        return false;
     1905                }
     1906
     1907                return $data;
     1908        }
     1909
     1910        /**
     1911         * Prepares arrays of value/format pairs as passed to wpdb CRUD methods.
     1912         *
     1913         * @since 4.2.0
     1914         * @access protected
     1915         *
     1916         * @param array $data   Array of fields to values.
     1917         * @param mixed $format Formats to be mapped to the values in $data.
     1918         * @return array Array, keyed by field names with values being an array
     1919         *               of 'value' and 'format' keys.
     1920         */
     1921        protected function process_field_formats( $data, $format ) {
     1922                $formats = $original_formats = (array) $format;
     1923
     1924                foreach ( $data as $field => $value ) {
     1925                        $value = array(
     1926                                'value'  => $value,
     1927                                'format' => '%s',
     1928                        );
     1929
     1930                        if ( ! empty( $format ) ) {
     1931                                $value['format'] = array_shift( $formats );
     1932                                if ( ! $value['format'] ) {
     1933                                        $value['format'] = reset( $original_formats );
     1934                                }
    17931935                        } elseif ( isset( $this->field_types[ $field ] ) ) {
    1794                                 $form = $this->field_types[ $field ];
     1936                                $value['format'] = $this->field_types[ $field ];
     1937                        }
     1938
     1939                        $data[ $field ] = $value;
     1940                }
     1941
     1942                return $data;
     1943        }
     1944
     1945        /**
     1946         * Adds field charsets to field/value/format arrays generated by
     1947         * the {@see wpdb::process_field_formats()} method.
     1948         *
     1949         * @since 4.2.0
     1950         * @access protected
     1951         *
     1952         * @param array  $data  As it comes from the {@see wpdb::process_field_formats()} method.
     1953         * @param string $table Table name.
     1954         * @return The same array as $data with additional 'charset' keys.
     1955         */
     1956        protected function process_field_charsets( $data, $table ) {
     1957                foreach ( $data as $field => $value ) {
     1958                        if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
     1959                                // We can skip this field if we know it isn't a string.
     1960                                // This checks %d/%f versus ! %s because it's sprintf() could take more.
     1961                                $value['charset'] = false;
     1962                        } elseif ( $this->check_ascii( $value['value'] ) ) {
     1963                                // If it's ASCII, then we don't need the charset. We can skip this field.
     1964                                $value['charset'] = false;
    17951965                        } else {
    1796                                 $form = '%s';
    1797                         }
    1798 
    1799                         $wheres[] = "$field = $form";
    1800                 }
    1801 
    1802                 $sql = "DELETE FROM $table WHERE " . implode( ' AND ', $wheres );
    1803                 return $this->query( $this->prepare( $sql, $where ) );
    1804         }
    1805 
     1966                                $value['charset'] = $this->get_col_charset( $table, $field );
     1967                                if ( is_wp_error( $value['charset'] ) ) {
     1968                                        return false;
     1969                                }
     1970
     1971                                // This isn't ASCII. Don't have strip_invalid_text() re-check.
     1972                                $value['ascii'] = false;
     1973                        }
     1974
     1975                        $data[ $field ] = $value;
     1976                }
     1977
     1978                return $data;
     1979        }
    18061980
    18071981        /**
     
    19512125        }
    19522126
     2127
     2128        /**
     2129         * Retrieves the character set for the given table.
     2130         *
     2131         * @since 4.2.0
     2132         * @access protected
     2133         *
     2134         * @param string $table Table name.
     2135         * @return string|WP_Error Table character set, {@see WP_Error} object if it couldn't be found.
     2136         */
     2137        protected function get_table_charset( $table ) {
     2138                $tablekey = strtolower( $table );
     2139
     2140                /**
     2141                 * Filter the table charset value before the DB is checked.
     2142                 *
     2143                 * Passing a non-null value to the filter will effectively short-circuit
     2144                 * checking the DB for the charset, returning that value instead.
     2145                 *
     2146                 * @since 4.2.0
     2147                 *
     2148                 * @param string $charset The character set to use. Default null.
     2149                 * @param string $table   The name of the table being checked.
     2150                 */
     2151                $charset = apply_filters( 'pre_get_table_charset', null, $table );
     2152                if ( null !== $charset ) {
     2153                        return $charset;
     2154                }
     2155
     2156                if ( isset( $this->table_charset[ $tablekey ] ) ) {
     2157                        return $this->table_charset[ $tablekey ];
     2158                }
     2159
     2160                $charsets = $columns = array();
     2161                $results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" );
     2162                if ( ! $results ) {
     2163                        return new WP_Error( 'wpdb_get_table_charset_failure' );
     2164                }
     2165
     2166                foreach ( $results as $column ) {
     2167                        $columns[ strtolower( $column->Field ) ] = $column;
     2168                }
     2169
     2170                $this->col_meta[ $tablekey ] = $columns;
     2171
     2172                foreach ( $columns as $column ) {
     2173                        if ( ! empty( $column->Collation ) ) {
     2174                                list( $charset ) = explode( '_', $column->Collation );
     2175                                $charsets[ strtolower( $charset ) ] = true;
     2176                        }
     2177
     2178                        list( $type ) = explode( '(', $column->Type );
     2179
     2180                        // A binary/blob means the whole query gets treated like this.
     2181                        if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) {
     2182                                $this->table_charset[ $tablekey ] = 'binary';
     2183                                return 'binary';
     2184                        }
     2185                }
     2186
     2187                // utf8mb3 is an alias for utf8.
     2188                if ( isset( $charsets['utf8mb3'] ) ) {
     2189                        $charsets['utf8'] = true;
     2190                        unset( $charsets['utf8mb3'] );
     2191                }
     2192
     2193                // Check if we have more than one charset in play.
     2194                $count = count( $charsets );
     2195                if ( 1 === $count ) {
     2196                        $charset = key( $charsets );
     2197                } elseif ( 0 === $count ) {
     2198                        // No charsets, assume this table can store whatever.
     2199                        $charset = false;
     2200                } else {
     2201                        // More than one charset. Remove latin1 if present and recalculate.
     2202                        unset( $charsets['latin1'] );
     2203                        $count = count( $charsets );
     2204                        if ( 1 === $count ) {
     2205                                // Only one charset (besides latin1).
     2206                                $charset = key( $charsets );
     2207                        } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
     2208                                // Two charsets, but they're utf8 and utf8mb4, use utf8.
     2209                                $charset = 'utf8';
     2210                        } else {
     2211                                // Two mixed character sets. ascii.
     2212                                $charset = 'ascii';
     2213                        }
     2214                }
     2215
     2216                $this->table_charset[ $tablekey ] = $charset;
     2217                return $charset;
     2218        }
     2219
     2220        /**
     2221         * Retrieves the character set for the given column.
     2222         *
     2223         * @since 4.2.0
     2224         * @access public
     2225         *
     2226         * @param string $table  Table name.
     2227         * @param string $column Column name.
     2228         * @return mixed Column character set as a string. False if the column has no
     2229         *               character set. {@see WP_Error} object if there was an error.
     2230         */
     2231        public function get_col_charset( $table, $column ) {
     2232                $tablekey = strtolower( $table );
     2233                $columnkey = strtolower( $column );
     2234
     2235                /**
     2236                 * Filter the column charset value before the DB is checked.
     2237                 *
     2238                 * Passing a non-null value to the filter will short-circuit
     2239                 * checking the DB for the charset, returning that value instead.
     2240                 *
     2241                 * @since 4.2.0
     2242                 *
     2243                 * @param string $charset The character set to use. Default null.
     2244                 * @param string $table   The name of the table being checked.
     2245                 * @param string $column  The name of the column being checked.
     2246                 */
     2247                $charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
     2248                if ( null !== $charset ) {
     2249                        return $charset;
     2250                }
     2251
     2252                // Skip this entirely if this isn't a MySQL database.
     2253                if ( false === $this->is_mysql ) {
     2254                        return false;
     2255                }
     2256
     2257                if ( empty( $this->table_charset[ $tablekey ] ) ) {
     2258                        // This primes column information for us.
     2259                        $table_charset = $this->get_table_charset( $table );
     2260                        if ( is_wp_error( $table_charset ) ) {
     2261                                return $table_charset;
     2262                        }
     2263                }
     2264
     2265                // If still no column information, return the table charset.
     2266                if ( empty( $this->col_meta[ $tablekey ] ) ) {
     2267                        return $this->table_charset[ $tablekey ];
     2268                }
     2269
     2270                // If this column doesn't exist, return the table charset.
     2271                if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
     2272                        return $this->table_charset[ $tablekey ];
     2273                }
     2274
     2275                // Return false when it's not a string column.
     2276                if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
     2277                        return false;
     2278                }
     2279
     2280                list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
     2281                return $charset;
     2282        }
     2283
     2284        /**
     2285         * Check if a string is ASCII.
     2286         *
     2287         * The negative regex is faster for non-ASCII strings, as it allows
     2288         * the search to finish as soon as it encounters a non-ASCII character.
     2289         *
     2290         * @since 4.2.0
     2291         * @access protected
     2292         *
     2293         * @param string $string String to check.
     2294         * @return bool True if ASCII, false if not.
     2295         */
     2296        protected function check_ascii( $string ) {
     2297                if ( function_exists( 'mb_check_encoding' ) ) {
     2298                        if ( mb_check_encoding( $string, 'ASCII' ) ) {
     2299                                return true;
     2300                        }
     2301                } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) {
     2302                        return true;
     2303                }
     2304
     2305                return false;
     2306        }
     2307
     2308        /**
     2309         * Check if the query is accessing a collation considered safe on the current version of MySQL.
     2310         *
     2311         * @since 4.2.0
     2312         * @access protected
     2313         *
     2314         * @param string $query The query to check.
     2315         * @return bool True if the collation is safe, false if it isn't.
     2316         */
     2317        protected function check_safe_collation( $query ) {
     2318                if ( $this->checking_collation ) {
     2319                        return true;
     2320                }
     2321                $table = $this->get_table_from_query( $query );
     2322                if ( ! $table ) {
     2323                        return false;
     2324                }
     2325
     2326                $this->checking_collation = true;
     2327                $this->get_table_charset( $table );
     2328                $this->checking_collation = false;
     2329
     2330                $table = strtolower( $table );
     2331                if ( empty( $this->col_meta[ $table ] ) ) {
     2332                        return false;
     2333                }
     2334
     2335                foreach( $this->col_meta[ $table ] as $col ) {
     2336                        if ( empty( $col->Collation ) ) {
     2337                                continue;
     2338                        }
     2339
     2340                        if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) {
     2341                                return false;
     2342                        }
     2343                }
     2344
     2345                return true;
     2346        }
     2347
     2348        /**
     2349         * Strips any invalid characters based on value/charset pairs.
     2350         *
     2351         * @since 4.2.0
     2352         * @access protected
     2353         *
     2354         * @param array $data Array of value arrays. Each value array has the keys
     2355         *                    'value' and 'charset'. An optional 'ascii' key can be
     2356         *                    set to false to avoid redundant ASCII checks.
     2357         * @return array|WP_Error The $data parameter, with invalid characters removed from
     2358         *                        each value. This works as a passthrough: any additional keys
     2359         *                        such as 'field' are retained in each value array. If we cannot
     2360         *                        remove invalid characters, a {@see WP_Error} object is returned.
     2361         */
     2362        protected function strip_invalid_text( $data ) {
     2363                // Some multibyte character sets that we can check in PHP.
     2364                $mb_charsets = array(
     2365                        'ascii'   => 'ASCII',
     2366                        'big5'    => 'BIG-5',
     2367                        'eucjpms' => 'eucJP-win',
     2368                        'gb2312'  => 'EUC-CN',
     2369                        'ujis'    => 'EUC-JP',
     2370                        'utf32'   => 'UTF-32',
     2371                );
     2372
     2373                $supported_charsets = array();
     2374                if ( function_exists( 'mb_list_encodings' ) ) {
     2375                        $supported_charsets = mb_list_encodings();
     2376                }
     2377
     2378                $db_check_string = false;
     2379
     2380                foreach ( $data as &$value ) {
     2381                        $charset = $value['charset'];
     2382
     2383                        // Column isn't a string, or is latin1, which will will happily store anything.
     2384                        if ( false === $charset || 'latin1' === $charset ) {
     2385                                continue;
     2386                        }
     2387
     2388                        if ( ! is_string( $value['value'] ) ) {
     2389                                continue;
     2390                        }
     2391
     2392                        // ASCII is always OK.
     2393                        if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) {
     2394                                continue;
     2395                        }
     2396
     2397                        // Convert the text locally.
     2398                        if ( $supported_charsets ) {
     2399                                if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) {
     2400                                        $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] );
     2401                                        continue;
     2402                                }
     2403                        }
     2404
     2405                        // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
     2406                        if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) {
     2407                                $regex = '/
     2408                                        (
     2409                                                (?: [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
     2410                                                |   [\xC2-\xDF][\x80-\xBF]       # double-byte sequences   110xxxxx 10xxxxxx
     2411                                                |   \xE0[\xA0-\xBF][\x80-\xBF]   # triple-byte sequences   1110xxxx 10xxxxxx * 2
     2412                                                |   [\xE1-\xEC][\x80-\xBF]{2}
     2413                                                |   \xED[\x80-\x9F][\x80-\xBF]
     2414                                                |   [\xEE-\xEF][\x80-\xBF]{2}';
     2415
     2416                                if ( 'utf8mb4' === $charset) {
     2417                                        $regex .= '
     2418                                                |    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
     2419                                                |    [\xF1-\xF3][\x80-\xBF]{3}
     2420                                                |    \xF4[\x80-\x8F][\x80-\xBF]{2}
     2421                                        ';
     2422                                }
     2423
     2424                                $regex .= '){1,50}                          # ...one or more times
     2425                                        )
     2426                                        | .                                  # anything else
     2427                                        /x';
     2428                                $value['value'] = preg_replace( $regex, '$1', $value['value'] );
     2429                                continue;
     2430                        }
     2431
     2432                        // We couldn't use any local conversions, send it to the DB.
     2433                        $value['db'] = $db_check_string = true;
     2434                }
     2435                unset( $value ); // Remove by reference.
     2436
     2437                if ( $db_check_string ) {
     2438                        $queries = array();
     2439                        foreach ( $data as $col => $value ) {
     2440                                if ( ! empty( $value['db'] ) ) {
     2441                                        if ( ! isset( $queries[ $value['charset'] ] ) ) {
     2442                                                $queries[ $value['charset'] ] = array();
     2443                                        }
     2444
     2445                                        // Split the CONVERT() calls by charset, so we can make sure the connection is right
     2446                                        $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] );
     2447                                }
     2448                        }
     2449
     2450                        $connection_charset = $this->charset;
     2451                        foreach ( $queries as $charset => $query ) {
     2452                                if ( ! $query ) {
     2453                                        continue;
     2454                                }
     2455
     2456                                // Change the charset to match the string(s) we're converting
     2457                                if ( $charset !== $connection_charset ) {
     2458                                        $connection_charset = $charset;
     2459                                        $this->set_charset( $this->dbh, $charset );
     2460                                }
     2461
     2462                                $this->check_current_query = false;
     2463
     2464                                $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N );
     2465                                if ( ! $row ) {
     2466                                        $this->set_charset( $this->dbh, $connection_charset );
     2467                                        return new WP_Error( 'wpdb_strip_invalid_text_failure' );
     2468                                }
     2469
     2470                                $cols = array_keys( $query );
     2471                                $col_count = count( $cols );
     2472                                for ( $ii = 0; $ii < $col_count; $ii++ ) {
     2473                                        $data[ $cols[ $ii ] ]['value'] = $row[ $ii ];
     2474                                }
     2475                        }
     2476
     2477                        // Don't forget to change the charset back!
     2478                        if ( $connection_charset !== $this->charset ) {
     2479                                $this->set_charset( $this->dbh );
     2480                        }
     2481                }
     2482
     2483                return $data;
     2484        }
     2485
     2486        /**
     2487         * Strips any invalid characters from the query.
     2488         *
     2489         * @since 4.2.0
     2490         * @access protected
     2491         *
     2492         * @param string $query Query to convert.
     2493         * @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails.
     2494         */
     2495        protected function strip_invalid_text_from_query( $query ) {
     2496                $table = $this->get_table_from_query( $query );
     2497                if ( $table ) {
     2498                        $charset = $this->get_table_charset( $table );
     2499                        if ( is_wp_error( $charset ) ) {
     2500                                return $charset;
     2501                        }
     2502
     2503                        // We can't reliably strip text from tables containing binary/blob columns
     2504                        if ( 'binary' === $charset ) {
     2505                                return $query;
     2506                        }
     2507                } else {
     2508                        $charset = $this->charset;
     2509                }
     2510
     2511                $data = array(
     2512                        'value'   => $query,
     2513                        'charset' => $charset,
     2514                        'ascii'   => false,
     2515                );
     2516
     2517                $data = $this->strip_invalid_text( array( $data ) );
     2518                if ( is_wp_error( $data ) ) {
     2519                        return $data;
     2520                }
     2521
     2522                return $data[0]['value'];
     2523        }
     2524
     2525        /**
     2526         * Strips any invalid characters from the string for a given table and column.
     2527         *
     2528         * @since 4.2.0
     2529         * @access public
     2530         *
     2531         * @param string $table  Table name.
     2532         * @param string $column Column name.
     2533         * @param string $value  The text to check.
     2534         * @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails.
     2535         */
     2536        public function strip_invalid_text_for_column( $table, $column, $value ) {
     2537                if ( ! is_string( $value ) || $this->check_ascii( $value ) ) {
     2538                        return $value;
     2539                }
     2540
     2541                $charset = $this->get_col_charset( $table, $column );
     2542                if ( ! $charset ) {
     2543                        // Not a string column.
     2544                        return $value;
     2545                } elseif ( is_wp_error( $charset ) ) {
     2546                        // Bail on real errors.
     2547                        return $charset;
     2548                }
     2549
     2550                $data = array(
     2551                        $column => array(
     2552                                'value'   => $value,
     2553                                'charset' => $charset,
     2554                                'ascii'   => false,
     2555                        )
     2556                );
     2557
     2558                $data = $this->strip_invalid_text( $data );
     2559                if ( is_wp_error( $data ) ) {
     2560                        return $data;
     2561                }
     2562
     2563                return $data[ $column ]['value'];
     2564        }
     2565
     2566        /**
     2567         * Find the first table name referenced in a query.
     2568         *
     2569         * @since 4.2.0
     2570         * @access protected
     2571         *
     2572         * @param string $query The query to search.
     2573         * @return string|false $table The table name found, or false if a table couldn't be found.
     2574         */
     2575        protected function get_table_from_query( $query ) {
     2576                // Remove characters that can legally trail the table name.
     2577                $query = rtrim( $query, ';/-#' );
     2578
     2579                // Allow (select...) union [...] style queries. Use the first query's table name.
     2580                $query = ltrim( $query, "\r\n\t (" );
     2581
     2582                /*
     2583                 * Strip everything between parentheses except nested selects and use only 1,000
     2584                 * chars of the query.
     2585                 */
     2586                $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', substr( $query, 0, 1000 ) );
     2587
     2588                // Quickly match most common queries.
     2589                if ( preg_match( '/^\s*(?:'
     2590                                . 'SELECT.*?\s+FROM'
     2591                                . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
     2592                                . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
     2593                                . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
     2594                                . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?'
     2595                                . ')\s+`?([\w-]+)`?/is', $query, $maybe ) ) {
     2596                        return $maybe[1];
     2597                }
     2598
     2599                // SHOW TABLE STATUS and SHOW TABLES
     2600                if ( preg_match( '/^\s*(?:'
     2601                                . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
     2602                                . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
     2603                                . ')\W([\w-]+)\W/is', $query, $maybe ) ) {
     2604                        return $maybe[1];
     2605                }
     2606
     2607                // Big pattern for the rest of the table-related queries.
     2608                if ( preg_match( '/^\s*(?:'
     2609                                . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
     2610                                . '|DESCRIBE|DESC|EXPLAIN|HANDLER'
     2611                                . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
     2612                                . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
     2613                                . '|TRUNCATE(?:\s+TABLE)?'
     2614                                . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
     2615                                . '|ALTER(?:\s+IGNORE)?\s+TABLE'
     2616                                . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
     2617                                . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
     2618                                . '|DROP\s+INDEX.*\s+ON'
     2619                                . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
     2620                                . '|(?:GRANT|REVOKE).*ON\s+TABLE'
     2621                                . '|SHOW\s+(?:.*FROM|.*TABLE)'
     2622                                . ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) {
     2623                        return $maybe[1];
     2624                }
     2625
     2626                return false;
     2627        }
     2628
    19532629        /**
    19542630         * Load the column metadata from the last query.
  • branches/3.9/tests/phpunit/includes/utils.php

    r26187 r32182  
    388388        remove_filter( 'get_the_terms', array( 'Featured_Content', 'hide_the_featured_term' ), 10, 3 );
    389389}
     390
     391/**
     392 * Special class for exposing protected wpdb methods we need to access
     393 */
     394class wpdb_exposed_methods_for_testing extends wpdb {
     395        public function __construct() {
     396                global $wpdb;
     397                $this->dbh = $wpdb->dbh;
     398                $this->use_mysqli = $wpdb->use_mysqli;
     399                $this->ready = true;
     400                $this->field_types = $wpdb->field_types;
     401                $this->charset = $wpdb->charset;
     402        }
     403
     404        public function __call( $name, $arguments ) {
     405                return call_user_func_array( array( $this, $name ), $arguments );
     406        }
     407}
  • branches/3.9/tests/phpunit/tests/db.php

    r27250 r32182  
    1515
    1616        /**
     17         * Our special WPDB
     18         * @var resource
     19         */
     20        protected static $_wpdb;
     21
     22        public static function setUpBeforeClass() {
     23                self::$_wpdb = new wpdb_exposed_methods_for_testing();
     24        }
     25
     26        /**
    1727         * Set up the test fixture
    1828         */
     
    2737         */
    2838        public function tearDown() {
     39                remove_filter( 'query', array( $this, 'query_filter' ) );
    2940                parent::tearDown();
    30                 remove_filter( 'query', array( $this, 'query_filter' ) );
    3141        }
    3242
     
    126136
    127137        /**
     138         * @ticket 21212
     139         */
     140        function test_wpdb_actually_protected_properties() {
     141                global $wpdb;
     142
     143                $new_meta = "HAHA I HOPE THIS DOESN'T WORK";
     144
     145                $col_meta = $wpdb->col_meta;
     146                $wpdb->col_meta = $new_meta;
     147
     148                $this->assertNotEquals( $col_meta, $new_meta );
     149                $this->assertEquals( $col_meta, $wpdb->col_meta );
     150        }
     151
     152        /**
    128153         * @ticket 18510
    129154         */
     
    153178         * @ticket 26847
    154179         */
    155         public function test_set_sql_mode() {
     180        function test_set_sql_mode() {
    156181                global $wpdb;
    157182
     
    159184
    160185                $new_modes = array( 'IGNORE_SPACE', 'NO_AUTO_CREATE_USER' );
     186
    161187                $wpdb->set_sql_mode( $new_modes );
     188
    162189                $check_new_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' );
    163                 $this->assertEquals( implode( ',', $new_modes ), $check_new_modes );
     190                $this->assertEqualSets( $new_modes, explode( ',', $check_new_modes ) );
    164191
    165192                $wpdb->set_sql_mode( explode( ',', $current_modes ) );
     
    170197         * @ticket 26847
    171198         */
    172         public function test_set_incompatible_sql_mode() {
     199        function test_set_incompatible_sql_mode() {
    173200                global $wpdb;
    174201
     
    178205                $wpdb->set_sql_mode( $new_modes );
    179206                $check_new_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' );
    180                 $this->assertFalse( in_array( 'NO_ZERO_DATE', explode( ',', $check_new_modes ) ) );
     207                $this->assertNotContains( 'NO_ZERO_DATE', explode( ',', $check_new_modes ) );
    181208
    182209                $wpdb->set_sql_mode( explode( ',', $current_modes ) );
     
    187214         * @ticket 26847
    188215         */
    189         public function test_set_allowed_incompatible_sql_mode() {
     216        function test_set_allowed_incompatible_sql_mode() {
    190217                global $wpdb;
    191218
    192219                $current_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' );
    193220
    194                 $new_modes = array( 'IGNORE_SPACE', 'NO_ZERO_DATE', 'NO_AUTO_CREATE_USER' );
     221                $new_modes = array( 'IGNORE_SPACE', 'ONLY_FULL_GROUP_BY', 'NO_AUTO_CREATE_USER' );
    195222
    196223                add_filter( 'incompatible_sql_modes', array( $this, 'filter_allowed_incompatible_sql_mode' ), 1, 1 );
     
    199226
    200227                $check_new_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' );
    201                 $this->assertTrue( in_array( 'NO_ZERO_DATE', explode( ',', $check_new_modes ) ) );
     228                $this->assertContains( 'ONLY_FULL_GROUP_BY', explode( ',', $check_new_modes ) );
    202229
    203230                $wpdb->set_sql_mode( explode( ',', $current_modes ) );
     
    205232
    206233        public function filter_allowed_incompatible_sql_mode( $modes ) {
    207                 $pos = array_search( 'NO_ZERO_DATE', $modes );
     234                $pos = array_search( 'ONLY_FULL_GROUP_BY', $modes );
    208235                $this->assertGreaterThanOrEqual( 0, $pos );
    209236
     
    227254                $this->assertEquals( "SELECT * FROM $wpdb->users WHERE id = 0", $prepared );
    228255        }
     256
     257        function test_db_version() {
     258                global $wpdb;
     259
     260                $this->assertTrue( version_compare( $wpdb->db_version(), '5.0', '>=' ) );
     261        }
     262
     263        function test_get_caller() {
     264                global $wpdb;
     265                $str = $wpdb->get_caller();
     266                $calls = explode( ', ', $str );
     267                $called = join( '->', array( __CLASS__, __FUNCTION__ ) );
     268                $this->assertEquals( $called, end( $calls ) );
     269        }
     270
     271        function test_has_cap() {
     272                global $wpdb;
     273                $this->assertTrue( $wpdb->has_cap( 'collation' ) );
     274                $this->assertTrue( $wpdb->has_cap( 'group_concat' ) );
     275                $this->assertTrue( $wpdb->has_cap( 'subqueries' ) );
     276                $this->assertTrue( $wpdb->has_cap( 'COLLATION' ) );
     277                $this->assertTrue( $wpdb->has_cap( 'GROUP_CONCAT' ) );
     278                $this->assertTrue( $wpdb->has_cap( 'SUBQUERIES' ) );
     279                $this->assertEquals(
     280                        version_compare( $wpdb->db_version(), '5.0.7', '>=' ),
     281                        $wpdb->has_cap( 'set_charset' )
     282                );
     283                $this->assertEquals(
     284                        version_compare( $wpdb->db_version(), '5.0.7', '>=' ),
     285                        $wpdb->has_cap( 'SET_CHARSET' )
     286                );
     287        }
     288
     289        /**
     290         * @expectedDeprecated supports_collation
     291         */
     292        function test_supports_collation() {
     293                global $wpdb;
     294                $this->assertTrue( $wpdb->supports_collation() );
     295        }
     296
     297        function test_check_database_version() {
     298                global $wpdb;
     299                $this->assertEmpty( $wpdb->check_database_version() );
     300        }
     301
     302        /**
     303         * @expectedException WPDieException
     304         */
     305        function test_bail() {
     306                global $wpdb;
     307                $wpdb->bail( 'Database is dead.' );
     308        }
     309
     310        function test_timers() {
     311                global $wpdb;
     312
     313                $wpdb->timer_start();
     314                usleep( 5 );
     315                $stop = $wpdb->timer_stop();
     316
     317                $this->assertNotEquals( $wpdb->time_start, $stop );
     318                $this->assertGreaterThan( $stop, $wpdb->time_start );
     319        }
     320
     321        function test_get_col_info() {
     322                global $wpdb;
     323
     324                $wpdb->get_results( "SELECT ID FROM $wpdb->users" );
     325
     326                $this->assertEquals( array( 'ID' ), $wpdb->get_col_info() );
     327                $this->assertEquals( array( $wpdb->users ), $wpdb->get_col_info( 'table' ) );
     328                $this->assertEquals( $wpdb->users, $wpdb->get_col_info( 'table', 0 ) );
     329        }
     330
     331        function test_query_and_delete() {
     332                global $wpdb;
     333                $rows = $wpdb->query( "INSERT INTO $wpdb->users (display_name) VALUES ('Walter Sobchak')" );
     334                $this->assertEquals( 1, $rows );
     335                $this->assertNotEmpty( $wpdb->insert_id );
     336                $d_rows = $wpdb->delete( $wpdb->users, array( 'ID' => $wpdb->insert_id ) );
     337                $this->assertEquals( 1, $d_rows );
     338        }
     339
     340        function test_get_row() {
     341                global $wpdb;
     342                $rows = $wpdb->query( "INSERT INTO $wpdb->users (display_name) VALUES ('Walter Sobchak')" );
     343                $this->assertEquals( 1, $rows );
     344                $this->assertNotEmpty( $wpdb->insert_id );
     345
     346                $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE ID = %d", $wpdb->insert_id ) );
     347                $this->assertInternalType( 'object', $row );
     348                $this->assertEquals( 'Walter Sobchak', $row->display_name );
     349        }
     350
     351        function test_replace() {
     352                global $wpdb;
     353                $rows1 = $wpdb->insert( $wpdb->users, array( 'display_name' => 'Walter Sobchak' ) );
     354                $this->assertEquals( 1, $rows1 );
     355                $this->assertNotEmpty( $wpdb->insert_id );
     356                $last = $wpdb->insert_id;
     357
     358                $rows2 = $wpdb->replace( $wpdb->users, array( 'ID' => $last, 'display_name' => 'Walter Replace Sobchak' ) );
     359                $this->assertEquals( 2, $rows2 );
     360                $this->assertNotEmpty( $wpdb->insert_id );
     361
     362                $this->assertEquals( $last, $wpdb->insert_id );
     363
     364                $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE ID = %d", $last ) );
     365                $this->assertEquals( 'Walter Replace Sobchak', $row->display_name );
     366        }
     367
     368        /**
     369         * wpdb::update() requires a WHERE condition.
     370         *
     371         * @ticket 26106
     372         */
     373        function test_empty_where_on_update() {
     374                global $wpdb;
     375                $suppress = $wpdb->suppress_errors( true );
     376                $wpdb->update( $wpdb->posts, array( 'post_name' => 'burrito' ), array() );
     377
     378                $expected1 = "UPDATE `{$wpdb->posts}` SET `post_name` = 'burrito' WHERE ";
     379                $this->assertNotEmpty( $wpdb->last_error );
     380                $this->assertEquals( $expected1, $wpdb->last_query );
     381
     382                $wpdb->update( $wpdb->posts, array( 'post_name' => 'burrito' ), array( 'post_status' => 'taco' ) );
     383
     384                $expected2 = "UPDATE `{$wpdb->posts}` SET `post_name` = 'burrito' WHERE `post_status` = 'taco'";
     385                $this->assertEmpty( $wpdb->last_error );
     386                $this->assertEquals( $expected2, $wpdb->last_query );
     387                $wpdb->suppress_errors( $suppress );
     388        }
     389
     390        /**
     391         * @ticket 21212
     392         */
     393        function data_get_table_from_query() {
     394                $table = 'a_test_table_name';
     395
     396                $queries = array(
     397                        // Basic
     398                        "SELECT * FROM $table",
     399                        "SELECT * FROM `$table`",
     400
     401                        "INSERT $table",
     402                        "INSERT IGNORE $table",
     403                        "INSERT IGNORE INTO $table",
     404                        "INSERT INTO $table",
     405                        "INSERT LOW_PRIORITY $table",
     406                        "INSERT DELAYED $table",
     407                        "INSERT HIGH_PRIORITY $table",
     408                        "INSERT LOW_PRIORITY IGNORE $table",
     409                        "INSERT LOW_PRIORITY INTO $table",
     410                        "INSERT LOW_PRIORITY IGNORE INTO $table",
     411
     412                        "REPLACE $table",
     413                        "REPLACE INTO $table",
     414                        "REPLACE LOW_PRIORITY $table",
     415                        "REPLACE DELAYED $table",
     416                        "REPLACE LOW_PRIORITY INTO $table",
     417
     418                        "UPDATE LOW_PRIORITY $table",
     419                        "UPDATE LOW_PRIORITY IGNORE $table",
     420
     421                        "DELETE $table",
     422                        "DELETE IGNORE $table",
     423                        "DELETE IGNORE FROM $table",
     424                        "DELETE FROM $table",
     425                        "DELETE LOW_PRIORITY $table",
     426                        "DELETE QUICK $table",
     427                        "DELETE IGNORE $table",
     428                        "DELETE LOW_PRIORITY FROM $table",
     429
     430                        // STATUS
     431                        "SHOW TABLE STATUS LIKE '$table'",
     432                        "SHOW TABLE STATUS WHERE NAME='$table'",
     433
     434                        "SHOW TABLES LIKE '$table'",
     435                        "SHOW FULL TABLES LIKE '$table'",
     436                        "SHOW TABLES WHERE NAME='$table'",
     437
     438                        // Extended
     439                        "EXPLAIN SELECT * FROM $table",
     440                        "EXPLAIN EXTENDED SELECT * FROM $table",
     441                        "EXPLAIN EXTENDED SELECT * FROM `$table`",
     442
     443                        "DESCRIBE $table",
     444                        "DESC $table",
     445                        "EXPLAIN $table",
     446                        "HANDLER $table",
     447
     448                        "LOCK TABLE $table",
     449                        "LOCK TABLES $table",
     450                        "UNLOCK TABLE $table",
     451
     452                        "RENAME TABLE $table",
     453                        "OPTIMIZE TABLE $table",
     454                        "BACKUP TABLE $table",
     455                        "RESTORE TABLE $table",
     456                        "CHECK TABLE $table",
     457                        "CHECKSUM TABLE $table",
     458                        "ANALYZE TABLE $table",
     459                        "REPAIR TABLE $table",
     460
     461                        "TRUNCATE $table",
     462                        "TRUNCATE TABLE $table",
     463
     464                        "CREATE TABLE $table",
     465                        "CREATE TEMPORARY TABLE $table",
     466                        "CREATE TABLE IF NOT EXISTS $table",
     467
     468                        "ALTER TABLE $table",
     469                        "ALTER IGNORE TABLE $table",
     470
     471                        "DROP TABLE $table",
     472                        "DROP TABLE IF EXISTS $table",
     473
     474                        "CREATE INDEX foo(bar(20)) ON $table",
     475                        "CREATE UNIQUE INDEX foo(bar(20)) ON $table",
     476                        "CREATE FULLTEXT INDEX foo(bar(20)) ON $table",
     477                        "CREATE SPATIAL INDEX foo(bar(20)) ON $table",
     478
     479                        "DROP INDEX foo ON $table",
     480
     481                        "LOAD DATA INFILE 'wp.txt' INTO TABLE $table",
     482                        "LOAD DATA LOW_PRIORITY INFILE 'wp.txt' INTO TABLE $table",
     483                        "LOAD DATA CONCURRENT INFILE 'wp.txt' INTO TABLE $table",
     484                        "LOAD DATA LOW_PRIORITY LOCAL INFILE 'wp.txt' INTO TABLE $table",
     485                        "LOAD DATA INFILE 'wp.txt' REPLACE INTO TABLE $table",
     486                        "LOAD DATA INFILE 'wp.txt' IGNORE INTO TABLE $table",
     487
     488                        "GRANT ALL ON TABLE $table",
     489                        "REVOKE ALL ON TABLE $table",
     490
     491                        "SHOW COLUMNS FROM $table",
     492                        "SHOW FULL COLUMNS FROM $table",
     493                        "SHOW CREATE TABLE $table",
     494                        "SHOW INDEX FROM $table",
     495                );
     496
     497                foreach ( $queries as &$query ) {
     498                        $query = array( $query, $table );
     499                }
     500                return $queries;
     501        }
     502
     503        /**
     504         * @dataProvider data_get_table_from_query
     505         * @ticket 21212
     506         */
     507        function test_get_table_from_query( $query, $table ) {
     508                $this->assertEquals( $table, self::$_wpdb->get_table_from_query( $query ) );
     509        }
     510
     511        function data_get_table_from_query_false() {
     512                $table = 'a_test_table_name';
     513                return array(
     514                        array( "LOL THIS ISN'T EVEN A QUERY $table" ),
     515                );
     516        }
     517
     518        /**
     519         * @dataProvider data_get_table_from_query_false
     520         * @ticket 21212
     521         */
     522        function test_get_table_from_query_false( $query ) {
     523                $this->assertFalse( self::$_wpdb->get_table_from_query( $query ) );
     524        }
     525
     526        /**
     527         * @ticket 21212
     528         */
     529        function data_process_field_formats() {
     530                $core_db_fields_no_format_specified = array(
     531                        array( 'post_content' => 'foo', 'post_parent' => 0 ),
     532                        null,
     533                        array(
     534                                'post_content' => array( 'value' => 'foo', 'format' => '%s' ),
     535                                'post_parent' => array( 'value' => 0, 'format' => '%d' ),
     536                        )
     537                );
     538
     539                $core_db_fields_formats_specified = array(
     540                        array( 'post_content' => 'foo', 'post_parent' => 0 ),
     541                        array( '%d', '%s' ), // These override core field_types
     542                        array(
     543                                'post_content' => array( 'value' => 'foo', 'format' => '%d' ),
     544                                'post_parent' => array( 'value' => 0, 'format' => '%s' ),
     545                        )
     546                );
     547
     548                $misc_fields_no_format_specified = array(
     549                        array( 'this_is_not_a_core_field' => 'foo', 'this_is_not_either' => 0 ),
     550                        null,
     551                        array(
     552                                'this_is_not_a_core_field' => array( 'value' => 'foo', 'format' => '%s' ),
     553                                'this_is_not_either' => array( 'value' => 0, 'format' => '%s' ),
     554                        )
     555                );
     556
     557                $misc_fields_formats_specified = array(
     558                        array( 'this_is_not_a_core_field' => 0, 'this_is_not_either' => 1.2 ),
     559                        array( '%d', '%f' ),
     560                        array(
     561                                'this_is_not_a_core_field' => array( 'value' => 0, 'format' => '%d' ),
     562                                'this_is_not_either' => array( 'value' => 1.2, 'format' => '%f' ),
     563                        )
     564                );
     565
     566                $misc_fields_insufficient_formats_specified = array(
     567                        array( 'this_is_not_a_core_field' => 0, 'this_is_not_either' => 's', 'nor_this' => 1 ),
     568                        array( '%d', '%s' ), // The first format is used for the third
     569                        array(
     570                                'this_is_not_a_core_field' => array( 'value' => 0, 'format' => '%d' ),
     571                                'this_is_not_either' => array( 'value' => 's', 'format' => '%s' ),
     572                                'nor_this' => array( 'value' => 1, 'format' => '%d' ),
     573                        )
     574                );
     575
     576                $vars = get_defined_vars();
     577                // Push the variable name onto the end for assertSame $message
     578                foreach ( $vars as $var_name => $var ) {
     579                        $vars[ $var_name ][] = $var_name;
     580                }
     581                return array_values( $vars );
     582        }
     583
     584        /**
     585         * @dataProvider data_process_field_formats
     586         * @ticket 21212
     587         */
     588        function test_process_field_formats( $data, $format, $expected, $message ) {
     589                $actual = self::$_wpdb->process_field_formats( $data, $format );
     590                $this->assertSame( $expected, $actual, $message );
     591        }
     592
     593        /**
     594         * @ticket 21212
     595         */
     596        function test_process_fields() {
     597                global $wpdb;
     598
     599                if ( $wpdb->charset ) {
     600                        $expected_charset = $wpdb->charset;
     601                } else {
     602                        $expected_charset = $wpdb->get_col_charset( $wpdb->posts, 'post_content' );
     603                }
     604
     605                if ( ! in_array( $expected_charset, array( 'utf8', 'utf8mb4', 'latin1' ) ) ) {
     606                        $this->markTestSkipped( "This test only works with utf8, utf8mb4 or latin1 character sets" );
     607                }
     608
     609                $data = array( 'post_content' => '¡foo foo foo!' );
     610                $expected = array(
     611                        'post_content' => array(
     612                                'value' => '¡foo foo foo!',
     613                                'format' => '%s',
     614                                'charset' => $expected_charset,
     615                                'ascii' => false,
     616                        )
     617                );
     618
     619                $this->assertSame( $expected, self::$_wpdb->process_fields( $wpdb->posts, $data, null ) );
     620        }
     621
     622        /**
     623         * @ticket 21212
     624         * @depends test_process_fields
     625         */
     626        function test_process_fields_on_nonexistent_table( $data ) {
     627                self::$_wpdb->suppress_errors( true );
     628                $data = array( 'post_content' => '¡foo foo foo!' );
     629                $this->assertFalse( self::$_wpdb->process_fields( 'nonexistent_table', $data, null ) );
     630                self::$_wpdb->suppress_errors( false );
     631        }
     632
     633        /**
     634         * @ticket 21212
     635         */
     636        function test_pre_get_table_charset_filter() {
     637                add_filter( 'pre_get_table_charset', array( $this, 'filter_pre_get_table_charset' ), 10, 2 );
     638                $charset = self::$_wpdb->get_table_charset( 'some_table' );
     639                remove_filter( 'pre_get_table_charset', array( $this, 'filter_pre_get_table_charset' ), 10 );
     640
     641                $this->assertEquals( $charset, 'fake_charset' );
     642        }
     643        function filter_pre_get_table_charset( $charset, $table ) {
     644                return 'fake_charset';
     645        }
     646
     647        /**
     648         * @ticket 21212
     649         */
     650        function test_pre_get_col_charset_filter() {
     651                add_filter( 'pre_get_col_charset', array( $this, 'filter_pre_get_col_charset' ), 10, 3 );
     652                $charset = self::$_wpdb->get_col_charset( 'some_table', 'some_col' );
     653                remove_filter( 'pre_get_col_charset', array( $this, 'filter_pre_get_col_charset' ), 10 );
     654
     655                $this->assertEquals( $charset, 'fake_col_charset' );
     656        }
     657        function filter_pre_get_col_charset( $charset, $table, $column ) {
     658                return 'fake_col_charset';
     659        }
    229660}
     661
  • branches/3.9/tests/phpunit/tests/post.php

    r27720 r32182  
    914914
    915915}
     916
     917        /**
     918         * @ticket 21212
     919         */
     920        function test_utf8mb3_post_saves_with_emoji() {
     921                global $wpdb;
     922                $_wpdb = new wpdb_exposed_methods_for_testing();
     923
     924                if ( 'utf8' !== $_wpdb->get_col_charset( $wpdb->posts, 'post_title' ) ) {
     925                        $this->markTestSkipped( 'This test is only useful with the utf8 character set' );
     926                }
     927
     928                require_once( ABSPATH . '/wp-admin/includes/post.php' );
     929
     930                $post_id = $this->factory->post->create();
     931
     932                $data = array(
     933                        'post_ID'      => $post_id,
     934                        'post_title'   => "foo\xf0\x9f\x98\x88bar",
     935                        'post_content' => "foo\xf0\x9f\x98\x8ebaz",
     936                        'post_excerpt' => "foo\xf0\x9f\x98\x90bat"
     937                );
     938
     939                $expected = array(
     940                        'post_title'   => "foobar",
     941                        'post_content' => "foobaz",
     942                        'post_excerpt' => "foobat"
     943                );
     944
     945                edit_post( $data );
     946
     947                $post = get_post( $post_id );
     948
     949                foreach( $expected as $field => $value ) {
     950                        $this->assertEquals( $post->$field, $value );
     951                }
     952        }
Note: See TracChangeset for help on using the changeset viewer.