Make WordPress Core

Changeset 32182


Ignore:
Timestamp:
04/20/2015 11:08:17 AM (10 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.