Changeset 32181
- Timestamp:
- 04/20/2015 10:53:33 AM (10 years ago)
- Location:
- branches/4.0
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/4.0/src/wp-admin/includes/post.php
r30410 r32181 178 178 */ 179 179 function edit_post( $post_data = null ) { 180 global $wpdb; 180 181 181 182 if ( empty($post_data) ) … … 318 319 update_post_meta( $post_ID, '_edit_last', get_current_user_id() ); 319 320 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 } 321 334 322 335 // Now that we have an ID we can fix any attachment anchor hrefs -
branches/4.0/src/wp-includes/wp-db.php
r29664 r32181 145 145 146 146 /** 147 * Cached column info, for sanity checking data before inserting 148 * 149 * @since 4.2.0 150 * @access protected 151 * @var array 152 */ 153 protected $col_meta = array(); 154 155 /** 156 * Calculated character sets on tables 157 * 158 * @since 4.2.0 159 * @access protected 160 * @var array 161 */ 162 protected $table_charset = array(); 163 164 /** 165 * Whether text fields in the current query need to be sanity checked. 166 * 167 * @since 4.2.0 168 * @access protected 169 * @var bool 170 */ 171 protected $check_current_query = true; 172 173 /** 174 * Flag to ensure we don't run into recursion problems when checking the collation. 175 * 176 * @since 4.2.0 177 * @access protected 178 * @see wpdb::check_safe_collation() 179 * @var boolean 180 */ 181 protected $checking_collation = false; 182 183 /** 147 184 * Saved info on the table column 148 185 * … … 648 685 */ 649 686 public function __set( $name, $value ) { 687 $protected_members = array( 688 'col_meta', 689 'table_charset', 690 'check_current_query', 691 ); 692 if ( in_array( $name, $protected_members, true ) ) { 693 return; 694 } 650 695 $this->$name = $value; 651 696 } … … 717 762 if ( ! empty( $collate ) ) 718 763 $query .= $this->prepare( ' COLLATE %s', $collate ); 719 mysqli_query( $ query, $dbh);764 mysqli_query( $dbh, $query ); 720 765 } 721 766 } else { … … 1534 1579 */ 1535 1580 public function query( $query ) { 1536 if ( ! $this->ready ) 1581 if ( ! $this->ready ) { 1582 $this->check_current_query = true; 1537 1583 return false; 1584 } 1538 1585 1539 1586 /** … … 1553 1600 // Log how the function was called 1554 1601 $this->func_call = "\$db->query(\"$query\")"; 1602 1603 // If we're writing to the database, make sure the query will write safely. 1604 if ( $this->check_current_query && ! $this->check_ascii( $query ) ) { 1605 $stripped_query = $this->strip_invalid_text_from_query( $query ); 1606 // strip_invalid_text_from_query() can perform queries, so we need 1607 // to flush again, just to make sure everything is clear. 1608 $this->flush(); 1609 if ( $stripped_query !== $query ) { 1610 $this->insert_id = 0; 1611 return false; 1612 } 1613 } 1614 1615 $this->check_current_query = true; 1555 1616 1556 1617 // Keep track of the last query for debug.. … … 1727 1788 */ 1728 1789 function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) { 1729 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) 1790 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) { 1730 1791 return false; 1792 } 1793 1794 $data = $this->process_fields( $table, $data, $format ); 1795 if ( false === $data ) { 1796 return false; 1797 } 1798 1799 $formats = $values = array(); 1800 foreach ( $data as $value ) { 1801 $formats[] = $value['format']; 1802 $values[] = $value['value']; 1803 } 1804 1805 $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`'; 1806 $formats = implode( ', ', $formats ); 1807 1808 $sql = "$type INTO `$table` ($fields) VALUES ($formats)"; 1809 1731 1810 $this->insert_id = 0; 1732 $formats = $format = (array) $format; 1733 $fields = array_keys( $data ); 1734 $formatted_fields = array(); 1735 foreach ( $fields as $field ) { 1736 if ( !empty( $format ) ) 1737 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0]; 1738 elseif ( isset( $this->field_types[$field] ) ) 1739 $form = $this->field_types[$field]; 1740 else 1741 $form = '%s'; 1742 $formatted_fields[] = $form; 1743 } 1744 $sql = "{$type} INTO `$table` (`" . implode( '`,`', $fields ) . "`) VALUES (" . implode( ",", $formatted_fields ) . ")"; 1745 return $this->query( $this->prepare( $sql, $data ) ); 1811 $this->check_current_query = false; 1812 return $this->query( $this->prepare( $sql, $values ) ); 1746 1813 } 1747 1814 … … 1768 1835 */ 1769 1836 public function update( $table, $data, $where, $format = null, $where_format = null ) { 1770 if ( ! is_array( $data ) || ! is_array( $where ) ) 1837 if ( ! is_array( $data ) || ! is_array( $where ) ) { 1771 1838 return false; 1772 1773 $formats = $format = (array) $format; 1774 $bits = $wheres = array(); 1775 foreach ( (array) array_keys( $data ) as $field ) { 1776 if ( !empty( $format ) ) 1777 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0]; 1778 elseif ( isset($this->field_types[$field]) ) 1779 $form = $this->field_types[$field]; 1780 else 1781 $form = '%s'; 1782 $bits[] = "`$field` = {$form}"; 1783 } 1784 1785 $where_formats = $where_format = (array) $where_format; 1786 foreach ( (array) array_keys( $where ) as $field ) { 1787 if ( !empty( $where_format ) ) 1788 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0]; 1789 elseif ( isset( $this->field_types[$field] ) ) 1790 $form = $this->field_types[$field]; 1791 else 1792 $form = '%s'; 1793 $wheres[] = "`$field` = {$form}"; 1794 } 1795 1796 $sql = "UPDATE `$table` SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres ); 1797 return $this->query( $this->prepare( $sql, array_merge( array_values( $data ), array_values( $where ) ) ) ); 1839 } 1840 1841 $data = $this->process_fields( $table, $data, $format ); 1842 if ( false === $data ) { 1843 return false; 1844 } 1845 $where = $this->process_fields( $table, $where, $where_format ); 1846 if ( false === $where ) { 1847 return false; 1848 } 1849 1850 $fields = $conditions = $values = array(); 1851 foreach ( $data as $field => $value ) { 1852 $fields[] = "`$field` = " . $value['format']; 1853 $values[] = $value['value']; 1854 } 1855 foreach ( $where as $field => $value ) { 1856 $conditions[] = "`$field` = " . $value['format']; 1857 $values[] = $value['value']; 1858 } 1859 1860 $fields = implode( ', ', $fields ); 1861 $conditions = implode( ' AND ', $conditions ); 1862 1863 $sql = "UPDATE `$table` SET $fields WHERE $conditions"; 1864 1865 $this->check_current_query = false; 1866 return $this->query( $this->prepare( $sql, $values ) ); 1798 1867 } 1799 1868 … … 1817 1886 */ 1818 1887 public function delete( $table, $where, $where_format = null ) { 1819 if ( ! is_array( $where ) ) 1888 if ( ! is_array( $where ) ) { 1820 1889 return false; 1821 1822 $wheres = array(); 1823 1824 $where_formats = $where_format = (array) $where_format; 1825 1826 foreach ( array_keys( $where ) as $field ) { 1827 if ( !empty( $where_format ) ) { 1828 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0]; 1890 } 1891 1892 $where = $this->process_fields( $table, $where, $where_format ); 1893 if ( false === $where ) { 1894 return false; 1895 } 1896 1897 $conditions = $values = array(); 1898 foreach ( $where as $field => $value ) { 1899 $conditions[] = "`$field` = " . $value['format']; 1900 $values[] = $value['value']; 1901 } 1902 1903 $conditions = implode( ' AND ', $conditions ); 1904 1905 $sql = "DELETE FROM `$table` WHERE $conditions"; 1906 1907 $this->check_current_query = false; 1908 return $this->query( $this->prepare( $sql, $values ) ); 1909 } 1910 1911 /** 1912 * Processes arrays of field/value pairs and field formats. 1913 * 1914 * This is a helper method for wpdb's CRUD methods, which take field/value 1915 * pairs for inserts, updates, and where clauses. This method first pairs 1916 * each value with a format. Then it determines the charset of that field, 1917 * using that to determine if any invalid text would be stripped. If text is 1918 * stripped, then field processing is rejected and the query fails. 1919 * 1920 * @since 4.2.0 1921 * @access protected 1922 * 1923 * @param string $table Table name. 1924 * @param array $data Field/value pair. 1925 * @param mixed $format Format for each field. 1926 * @return array|bool Returns an array of fields that contain paired values 1927 * and formats. Returns false for invalid values. 1928 */ 1929 protected function process_fields( $table, $data, $format ) { 1930 $data = $this->process_field_formats( $data, $format ); 1931 $data = $this->process_field_charsets( $data, $table ); 1932 if ( false === $data ) { 1933 return false; 1934 } 1935 1936 $converted_data = $this->strip_invalid_text( $data ); 1937 1938 if ( $data !== $converted_data ) { 1939 return false; 1940 } 1941 1942 return $data; 1943 } 1944 1945 /** 1946 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods. 1947 * 1948 * @since 4.2.0 1949 * @access protected 1950 * 1951 * @param array $data Array of fields to values. 1952 * @param mixed $format Formats to be mapped to the values in $data. 1953 * @return array Array, keyed by field names with values being an array 1954 * of 'value' and 'format' keys. 1955 */ 1956 protected function process_field_formats( $data, $format ) { 1957 $formats = $original_formats = (array) $format; 1958 1959 foreach ( $data as $field => $value ) { 1960 $value = array( 1961 'value' => $value, 1962 'format' => '%s', 1963 ); 1964 1965 if ( ! empty( $format ) ) { 1966 $value['format'] = array_shift( $formats ); 1967 if ( ! $value['format'] ) { 1968 $value['format'] = reset( $original_formats ); 1969 } 1829 1970 } elseif ( isset( $this->field_types[ $field ] ) ) { 1830 $form = $this->field_types[ $field ]; 1971 $value['format'] = $this->field_types[ $field ]; 1972 } 1973 1974 $data[ $field ] = $value; 1975 } 1976 1977 return $data; 1978 } 1979 1980 /** 1981 * Adds field charsets to field/value/format arrays generated by 1982 * the {@see wpdb::process_field_formats()} method. 1983 * 1984 * @since 4.2.0 1985 * @access protected 1986 * 1987 * @param array $data As it comes from the {@see wpdb::process_field_formats()} method. 1988 * @param string $table Table name. 1989 * @return The same array as $data with additional 'charset' keys. 1990 */ 1991 protected function process_field_charsets( $data, $table ) { 1992 foreach ( $data as $field => $value ) { 1993 if ( '%d' === $value['format'] || '%f' === $value['format'] ) { 1994 // We can skip this field if we know it isn't a string. 1995 // This checks %d/%f versus ! %s because it's sprintf() could take more. 1996 $value['charset'] = false; 1997 } elseif ( $this->check_ascii( $value['value'] ) ) { 1998 // If it's ASCII, then we don't need the charset. We can skip this field. 1999 $value['charset'] = false; 1831 2000 } else { 1832 $form = '%s'; 1833 } 1834 1835 $wheres[] = "$field = $form"; 1836 } 1837 1838 $sql = "DELETE FROM $table WHERE " . implode( ' AND ', $wheres ); 1839 return $this->query( $this->prepare( $sql, $where ) ); 1840 } 1841 2001 $value['charset'] = $this->get_col_charset( $table, $field ); 2002 if ( is_wp_error( $value['charset'] ) ) { 2003 return false; 2004 } 2005 2006 // This isn't ASCII. Don't have strip_invalid_text() re-check. 2007 $value['ascii'] = false; 2008 } 2009 2010 $data[ $field ] = $value; 2011 } 2012 2013 return $data; 2014 } 1842 2015 1843 2016 /** … … 1987 2160 } 1988 2161 2162 2163 /** 2164 * Retrieves the character set for the given table. 2165 * 2166 * @since 4.2.0 2167 * @access protected 2168 * 2169 * @param string $table Table name. 2170 * @return string|WP_Error Table character set, {@see WP_Error} object if it couldn't be found. 2171 */ 2172 protected function get_table_charset( $table ) { 2173 $tablekey = strtolower( $table ); 2174 2175 /** 2176 * Filter the table charset value before the DB is checked. 2177 * 2178 * Passing a non-null value to the filter will effectively short-circuit 2179 * checking the DB for the charset, returning that value instead. 2180 * 2181 * @since 4.2.0 2182 * 2183 * @param string $charset The character set to use. Default null. 2184 * @param string $table The name of the table being checked. 2185 */ 2186 $charset = apply_filters( 'pre_get_table_charset', null, $table ); 2187 if ( null !== $charset ) { 2188 return $charset; 2189 } 2190 2191 if ( isset( $this->table_charset[ $tablekey ] ) ) { 2192 return $this->table_charset[ $tablekey ]; 2193 } 2194 2195 $charsets = $columns = array(); 2196 $results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" ); 2197 if ( ! $results ) { 2198 return new WP_Error( 'wpdb_get_table_charset_failure' ); 2199 } 2200 2201 foreach ( $results as $column ) { 2202 $columns[ strtolower( $column->Field ) ] = $column; 2203 } 2204 2205 $this->col_meta[ $tablekey ] = $columns; 2206 2207 foreach ( $columns as $column ) { 2208 if ( ! empty( $column->Collation ) ) { 2209 list( $charset ) = explode( '_', $column->Collation ); 2210 $charsets[ strtolower( $charset ) ] = true; 2211 } 2212 2213 list( $type ) = explode( '(', $column->Type ); 2214 2215 // A binary/blob means the whole query gets treated like this. 2216 if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) { 2217 $this->table_charset[ $tablekey ] = 'binary'; 2218 return 'binary'; 2219 } 2220 } 2221 2222 // utf8mb3 is an alias for utf8. 2223 if ( isset( $charsets['utf8mb3'] ) ) { 2224 $charsets['utf8'] = true; 2225 unset( $charsets['utf8mb3'] ); 2226 } 2227 2228 // Check if we have more than one charset in play. 2229 $count = count( $charsets ); 2230 if ( 1 === $count ) { 2231 $charset = key( $charsets ); 2232 } elseif ( 0 === $count ) { 2233 // No charsets, assume this table can store whatever. 2234 $charset = false; 2235 } else { 2236 // More than one charset. Remove latin1 if present and recalculate. 2237 unset( $charsets['latin1'] ); 2238 $count = count( $charsets ); 2239 if ( 1 === $count ) { 2240 // Only one charset (besides latin1). 2241 $charset = key( $charsets ); 2242 } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) { 2243 // Two charsets, but they're utf8 and utf8mb4, use utf8. 2244 $charset = 'utf8'; 2245 } else { 2246 // Two mixed character sets. ascii. 2247 $charset = 'ascii'; 2248 } 2249 } 2250 2251 $this->table_charset[ $tablekey ] = $charset; 2252 return $charset; 2253 } 2254 2255 /** 2256 * Retrieves the character set for the given column. 2257 * 2258 * @since 4.2.0 2259 * @access public 2260 * 2261 * @param string $table Table name. 2262 * @param string $column Column name. 2263 * @return mixed Column character set as a string. False if the column has no 2264 * character set. {@see WP_Error} object if there was an error. 2265 */ 2266 public function get_col_charset( $table, $column ) { 2267 $tablekey = strtolower( $table ); 2268 $columnkey = strtolower( $column ); 2269 2270 /** 2271 * Filter the column charset value before the DB is checked. 2272 * 2273 * Passing a non-null value to the filter will short-circuit 2274 * checking the DB for the charset, returning that value instead. 2275 * 2276 * @since 4.2.0 2277 * 2278 * @param string $charset The character set to use. Default null. 2279 * @param string $table The name of the table being checked. 2280 * @param string $column The name of the column being checked. 2281 */ 2282 $charset = apply_filters( 'pre_get_col_charset', null, $table, $column ); 2283 if ( null !== $charset ) { 2284 return $charset; 2285 } 2286 2287 // Skip this entirely if this isn't a MySQL database. 2288 if ( false === $this->is_mysql ) { 2289 return false; 2290 } 2291 2292 if ( empty( $this->table_charset[ $tablekey ] ) ) { 2293 // This primes column information for us. 2294 $table_charset = $this->get_table_charset( $table ); 2295 if ( is_wp_error( $table_charset ) ) { 2296 return $table_charset; 2297 } 2298 } 2299 2300 // If still no column information, return the table charset. 2301 if ( empty( $this->col_meta[ $tablekey ] ) ) { 2302 return $this->table_charset[ $tablekey ]; 2303 } 2304 2305 // If this column doesn't exist, return the table charset. 2306 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { 2307 return $this->table_charset[ $tablekey ]; 2308 } 2309 2310 // Return false when it's not a string column. 2311 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) { 2312 return false; 2313 } 2314 2315 list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation ); 2316 return $charset; 2317 } 2318 2319 /** 2320 * Check if a string is ASCII. 2321 * 2322 * The negative regex is faster for non-ASCII strings, as it allows 2323 * the search to finish as soon as it encounters a non-ASCII character. 2324 * 2325 * @since 4.2.0 2326 * @access protected 2327 * 2328 * @param string $string String to check. 2329 * @return bool True if ASCII, false if not. 2330 */ 2331 protected function check_ascii( $string ) { 2332 if ( function_exists( 'mb_check_encoding' ) ) { 2333 if ( mb_check_encoding( $string, 'ASCII' ) ) { 2334 return true; 2335 } 2336 } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) { 2337 return true; 2338 } 2339 2340 return false; 2341 } 2342 2343 /** 2344 * Check if the query is accessing a collation considered safe on the current version of MySQL. 2345 * 2346 * @since 4.2.0 2347 * @access protected 2348 * 2349 * @param string $query The query to check. 2350 * @return bool True if the collation is safe, false if it isn't. 2351 */ 2352 protected function check_safe_collation( $query ) { 2353 if ( $this->checking_collation ) { 2354 return true; 2355 } 2356 $table = $this->get_table_from_query( $query ); 2357 if ( ! $table ) { 2358 return false; 2359 } 2360 2361 $this->checking_collation = true; 2362 $this->get_table_charset( $table ); 2363 $this->checking_collation = false; 2364 2365 $table = strtolower( $table ); 2366 if ( empty( $this->col_meta[ $table ] ) ) { 2367 return false; 2368 } 2369 2370 foreach( $this->col_meta[ $table ] as $col ) { 2371 if ( empty( $col->Collation ) ) { 2372 continue; 2373 } 2374 2375 if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) { 2376 return false; 2377 } 2378 } 2379 2380 return true; 2381 } 2382 2383 /** 2384 * Strips any invalid characters based on value/charset pairs. 2385 * 2386 * @since 4.2.0 2387 * @access protected 2388 * 2389 * @param array $data Array of value arrays. Each value array has the keys 2390 * 'value' and 'charset'. An optional 'ascii' key can be 2391 * set to false to avoid redundant ASCII checks. 2392 * @return array|WP_Error The $data parameter, with invalid characters removed from 2393 * each value. This works as a passthrough: any additional keys 2394 * such as 'field' are retained in each value array. If we cannot 2395 * remove invalid characters, a {@see WP_Error} object is returned. 2396 */ 2397 protected function strip_invalid_text( $data ) { 2398 // Some multibyte character sets that we can check in PHP. 2399 $mb_charsets = array( 2400 'ascii' => 'ASCII', 2401 'big5' => 'BIG-5', 2402 'eucjpms' => 'eucJP-win', 2403 'gb2312' => 'EUC-CN', 2404 'ujis' => 'EUC-JP', 2405 'utf32' => 'UTF-32', 2406 ); 2407 2408 $supported_charsets = array(); 2409 if ( function_exists( 'mb_list_encodings' ) ) { 2410 $supported_charsets = mb_list_encodings(); 2411 } 2412 2413 $db_check_string = false; 2414 2415 foreach ( $data as &$value ) { 2416 $charset = $value['charset']; 2417 2418 // Column isn't a string, or is latin1, which will will happily store anything. 2419 if ( false === $charset || 'latin1' === $charset ) { 2420 continue; 2421 } 2422 2423 if ( ! is_string( $value['value'] ) ) { 2424 continue; 2425 } 2426 2427 // ASCII is always OK. 2428 if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) { 2429 continue; 2430 } 2431 2432 // Convert the text locally. 2433 if ( $supported_charsets ) { 2434 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) { 2435 $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] ); 2436 continue; 2437 } 2438 } 2439 2440 // utf8 can be handled by regex, which is a bunch faster than a DB lookup. 2441 if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) { 2442 $regex = '/ 2443 ( 2444 (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx 2445 | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx 2446 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 2447 | [\xE1-\xEC][\x80-\xBF]{2} 2448 | \xED[\x80-\x9F][\x80-\xBF] 2449 | [\xEE-\xEF][\x80-\xBF]{2}'; 2450 2451 if ( 'utf8mb4' === $charset) { 2452 $regex .= ' 2453 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 2454 | [\xF1-\xF3][\x80-\xBF]{3} 2455 | \xF4[\x80-\x8F][\x80-\xBF]{2} 2456 '; 2457 } 2458 2459 $regex .= '){1,50} # ...one or more times 2460 ) 2461 | . # anything else 2462 /x'; 2463 $value['value'] = preg_replace( $regex, '$1', $value['value'] ); 2464 continue; 2465 } 2466 2467 // We couldn't use any local conversions, send it to the DB. 2468 $value['db'] = $db_check_string = true; 2469 } 2470 unset( $value ); // Remove by reference. 2471 2472 if ( $db_check_string ) { 2473 $queries = array(); 2474 foreach ( $data as $col => $value ) { 2475 if ( ! empty( $value['db'] ) ) { 2476 if ( ! isset( $queries[ $value['charset'] ] ) ) { 2477 $queries[ $value['charset'] ] = array(); 2478 } 2479 2480 // Split the CONVERT() calls by charset, so we can make sure the connection is right 2481 $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] ); 2482 } 2483 } 2484 2485 $connection_charset = $this->charset; 2486 foreach ( $queries as $charset => $query ) { 2487 if ( ! $query ) { 2488 continue; 2489 } 2490 2491 // Change the charset to match the string(s) we're converting 2492 if ( $charset !== $connection_charset ) { 2493 $connection_charset = $charset; 2494 $this->set_charset( $this->dbh, $charset ); 2495 } 2496 2497 $this->check_current_query = false; 2498 2499 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N ); 2500 if ( ! $row ) { 2501 $this->set_charset( $this->dbh, $connection_charset ); 2502 return new WP_Error( 'wpdb_strip_invalid_text_failure' ); 2503 } 2504 2505 $cols = array_keys( $query ); 2506 $col_count = count( $cols ); 2507 for ( $ii = 0; $ii < $col_count; $ii++ ) { 2508 $data[ $cols[ $ii ] ]['value'] = $row[ $ii ]; 2509 } 2510 } 2511 2512 // Don't forget to change the charset back! 2513 if ( $connection_charset !== $this->charset ) { 2514 $this->set_charset( $this->dbh ); 2515 } 2516 } 2517 2518 return $data; 2519 } 2520 2521 /** 2522 * Strips any invalid characters from the query. 2523 * 2524 * @since 4.2.0 2525 * @access protected 2526 * 2527 * @param string $query Query to convert. 2528 * @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails. 2529 */ 2530 protected function strip_invalid_text_from_query( $query ) { 2531 $table = $this->get_table_from_query( $query ); 2532 if ( $table ) { 2533 $charset = $this->get_table_charset( $table ); 2534 if ( is_wp_error( $charset ) ) { 2535 return $charset; 2536 } 2537 2538 // We can't reliably strip text from tables containing binary/blob columns 2539 if ( 'binary' === $charset ) { 2540 return $query; 2541 } 2542 } else { 2543 $charset = $this->charset; 2544 } 2545 2546 $data = array( 2547 'value' => $query, 2548 'charset' => $charset, 2549 'ascii' => false, 2550 ); 2551 2552 $data = $this->strip_invalid_text( array( $data ) ); 2553 if ( is_wp_error( $data ) ) { 2554 return $data; 2555 } 2556 2557 return $data[0]['value']; 2558 } 2559 2560 /** 2561 * Strips any invalid characters from the string for a given table and column. 2562 * 2563 * @since 4.2.0 2564 * @access public 2565 * 2566 * @param string $table Table name. 2567 * @param string $column Column name. 2568 * @param string $value The text to check. 2569 * @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails. 2570 */ 2571 public function strip_invalid_text_for_column( $table, $column, $value ) { 2572 if ( ! is_string( $value ) || $this->check_ascii( $value ) ) { 2573 return $value; 2574 } 2575 2576 $charset = $this->get_col_charset( $table, $column ); 2577 if ( ! $charset ) { 2578 // Not a string column. 2579 return $value; 2580 } elseif ( is_wp_error( $charset ) ) { 2581 // Bail on real errors. 2582 return $charset; 2583 } 2584 2585 $data = array( 2586 $column => array( 2587 'value' => $value, 2588 'charset' => $charset, 2589 'ascii' => false, 2590 ) 2591 ); 2592 2593 $data = $this->strip_invalid_text( $data ); 2594 if ( is_wp_error( $data ) ) { 2595 return $data; 2596 } 2597 2598 return $data[ $column ]['value']; 2599 } 2600 2601 /** 2602 * Find the first table name referenced in a query. 2603 * 2604 * @since 4.2.0 2605 * @access protected 2606 * 2607 * @param string $query The query to search. 2608 * @return string|false $table The table name found, or false if a table couldn't be found. 2609 */ 2610 protected function get_table_from_query( $query ) { 2611 // Remove characters that can legally trail the table name. 2612 $query = rtrim( $query, ';/-#' ); 2613 2614 // Allow (select...) union [...] style queries. Use the first query's table name. 2615 $query = ltrim( $query, "\r\n\t (" ); 2616 2617 /* 2618 * Strip everything between parentheses except nested selects and use only 1,000 2619 * chars of the query. 2620 */ 2621 $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', substr( $query, 0, 1000 ) ); 2622 2623 // Quickly match most common queries. 2624 if ( preg_match( '/^\s*(?:' 2625 . 'SELECT.*?\s+FROM' 2626 . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?' 2627 . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?' 2628 . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?' 2629 . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?' 2630 . ')\s+`?([\w-]+)`?/is', $query, $maybe ) ) { 2631 return $maybe[1]; 2632 } 2633 2634 // SHOW TABLE STATUS and SHOW TABLES 2635 if ( preg_match( '/^\s*(?:' 2636 . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' 2637 . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' 2638 . ')\W([\w-]+)\W/is', $query, $maybe ) ) { 2639 return $maybe[1]; 2640 } 2641 2642 // Big pattern for the rest of the table-related queries. 2643 if ( preg_match( '/^\s*(?:' 2644 . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM' 2645 . '|DESCRIBE|DESC|EXPLAIN|HANDLER' 2646 . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?' 2647 . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE' 2648 . '|TRUNCATE(?:\s+TABLE)?' 2649 . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?' 2650 . '|ALTER(?:\s+IGNORE)?\s+TABLE' 2651 . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?' 2652 . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON' 2653 . '|DROP\s+INDEX.*\s+ON' 2654 . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE' 2655 . '|(?:GRANT|REVOKE).*ON\s+TABLE' 2656 . '|SHOW\s+(?:.*FROM|.*TABLE)' 2657 . ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) { 2658 return $maybe[1]; 2659 } 2660 2661 return false; 2662 } 2663 1989 2664 /** 1990 2665 * Load the column metadata from the last query. -
branches/4.0/tests/phpunit/tests/db.php
r29701 r32181 15 15 16 16 /** 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 /** 17 27 * Set up the test fixture 18 28 */ … … 27 37 */ 28 38 public function tearDown() { 39 remove_filter( 'query', array( $this, 'query_filter' ) ); 29 40 parent::tearDown(); 30 remove_filter( 'query', array( $this, 'query_filter' ) );31 41 } 32 42 … … 225 235 226 236 /** 237 * @ticket 21212 238 */ 239 function test_wpdb_actually_protected_properties() { 240 global $wpdb; 241 242 $new_meta = "HAHA I HOPE THIS DOESN'T WORK"; 243 244 $col_meta = $wpdb->col_meta; 245 $wpdb->col_meta = $new_meta; 246 247 $this->assertNotEquals( $col_meta, $new_meta ); 248 $this->assertEquals( $col_meta, $wpdb->col_meta ); 249 } 250 251 /** 227 252 * @ticket 18510 228 253 */ … … 258 283 259 284 $new_modes = array( 'IGNORE_SPACE', 'NO_AUTO_CREATE_USER' ); 285 260 286 $wpdb->set_sql_mode( $new_modes ); 287 261 288 $check_new_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' ); 262 $this->assertEqual s( implode( ',', $new_modes ), $check_new_modes);289 $this->assertEqualSets( $new_modes, explode( ',', $check_new_modes ) ); 263 290 264 291 $wpdb->set_sql_mode( explode( ',', $current_modes ) ); … … 277 304 $wpdb->set_sql_mode( $new_modes ); 278 305 $check_new_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' ); 279 $this->assert False( in_array( 'NO_ZERO_DATE', explode( ',', $check_new_modes )) );306 $this->assertNotContains( 'NO_ZERO_DATE', explode( ',', $check_new_modes ) ); 280 307 281 308 $wpdb->set_sql_mode( explode( ',', $current_modes ) ); … … 291 318 $current_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' ); 292 319 293 $new_modes = array( 'IGNORE_SPACE', ' NO_ZERO_DATE', 'NO_AUTO_CREATE_USER' );320 $new_modes = array( 'IGNORE_SPACE', 'ONLY_FULL_GROUP_BY', 'NO_AUTO_CREATE_USER' ); 294 321 295 322 add_filter( 'incompatible_sql_modes', array( $this, 'filter_allowed_incompatible_sql_mode' ), 1, 1 ); … … 298 325 299 326 $check_new_modes = $wpdb->get_var( 'SELECT @@SESSION.sql_mode;' ); 300 $this->assert True( in_array( 'NO_ZERO_DATE', explode( ',', $check_new_modes )) );327 $this->assertContains( 'ONLY_FULL_GROUP_BY', explode( ',', $check_new_modes ) ); 301 328 302 329 $wpdb->set_sql_mode( explode( ',', $current_modes ) ); … … 304 331 305 332 public function filter_allowed_incompatible_sql_mode( $modes ) { 306 $pos = array_search( ' NO_ZERO_DATE', $modes );333 $pos = array_search( 'ONLY_FULL_GROUP_BY', $modes ); 307 334 $this->assertGreaterThanOrEqual( 0, $pos ); 308 335 … … 459 486 $wpdb->suppress_errors( $suppress ); 460 487 } 488 489 /** 490 * @ticket 21212 491 */ 492 function data_get_table_from_query() { 493 $table = 'a_test_table_name'; 494 495 $queries = array( 496 // Basic 497 "SELECT * FROM $table", 498 "SELECT * FROM `$table`", 499 500 "INSERT $table", 501 "INSERT IGNORE $table", 502 "INSERT IGNORE INTO $table", 503 "INSERT INTO $table", 504 "INSERT LOW_PRIORITY $table", 505 "INSERT DELAYED $table", 506 "INSERT HIGH_PRIORITY $table", 507 "INSERT LOW_PRIORITY IGNORE $table", 508 "INSERT LOW_PRIORITY INTO $table", 509 "INSERT LOW_PRIORITY IGNORE INTO $table", 510 511 "REPLACE $table", 512 "REPLACE INTO $table", 513 "REPLACE LOW_PRIORITY $table", 514 "REPLACE DELAYED $table", 515 "REPLACE LOW_PRIORITY INTO $table", 516 517 "UPDATE LOW_PRIORITY $table", 518 "UPDATE LOW_PRIORITY IGNORE $table", 519 520 "DELETE $table", 521 "DELETE IGNORE $table", 522 "DELETE IGNORE FROM $table", 523 "DELETE FROM $table", 524 "DELETE LOW_PRIORITY $table", 525 "DELETE QUICK $table", 526 "DELETE IGNORE $table", 527 "DELETE LOW_PRIORITY FROM $table", 528 529 // STATUS 530 "SHOW TABLE STATUS LIKE '$table'", 531 "SHOW TABLE STATUS WHERE NAME='$table'", 532 533 "SHOW TABLES LIKE '$table'", 534 "SHOW FULL TABLES LIKE '$table'", 535 "SHOW TABLES WHERE NAME='$table'", 536 537 // Extended 538 "EXPLAIN SELECT * FROM $table", 539 "EXPLAIN EXTENDED SELECT * FROM $table", 540 "EXPLAIN EXTENDED SELECT * FROM `$table`", 541 542 "DESCRIBE $table", 543 "DESC $table", 544 "EXPLAIN $table", 545 "HANDLER $table", 546 547 "LOCK TABLE $table", 548 "LOCK TABLES $table", 549 "UNLOCK TABLE $table", 550 551 "RENAME TABLE $table", 552 "OPTIMIZE TABLE $table", 553 "BACKUP TABLE $table", 554 "RESTORE TABLE $table", 555 "CHECK TABLE $table", 556 "CHECKSUM TABLE $table", 557 "ANALYZE TABLE $table", 558 "REPAIR TABLE $table", 559 560 "TRUNCATE $table", 561 "TRUNCATE TABLE $table", 562 563 "CREATE TABLE $table", 564 "CREATE TEMPORARY TABLE $table", 565 "CREATE TABLE IF NOT EXISTS $table", 566 567 "ALTER TABLE $table", 568 "ALTER IGNORE TABLE $table", 569 570 "DROP TABLE $table", 571 "DROP TABLE IF EXISTS $table", 572 573 "CREATE INDEX foo(bar(20)) ON $table", 574 "CREATE UNIQUE INDEX foo(bar(20)) ON $table", 575 "CREATE FULLTEXT INDEX foo(bar(20)) ON $table", 576 "CREATE SPATIAL INDEX foo(bar(20)) ON $table", 577 578 "DROP INDEX foo ON $table", 579 580 "LOAD DATA INFILE 'wp.txt' INTO TABLE $table", 581 "LOAD DATA LOW_PRIORITY INFILE 'wp.txt' INTO TABLE $table", 582 "LOAD DATA CONCURRENT INFILE 'wp.txt' INTO TABLE $table", 583 "LOAD DATA LOW_PRIORITY LOCAL INFILE 'wp.txt' INTO TABLE $table", 584 "LOAD DATA INFILE 'wp.txt' REPLACE INTO TABLE $table", 585 "LOAD DATA INFILE 'wp.txt' IGNORE INTO TABLE $table", 586 587 "GRANT ALL ON TABLE $table", 588 "REVOKE ALL ON TABLE $table", 589 590 "SHOW COLUMNS FROM $table", 591 "SHOW FULL COLUMNS FROM $table", 592 "SHOW CREATE TABLE $table", 593 "SHOW INDEX FROM $table", 594 ); 595 596 foreach ( $queries as &$query ) { 597 $query = array( $query, $table ); 598 } 599 return $queries; 600 } 601 602 /** 603 * @dataProvider data_get_table_from_query 604 * @ticket 21212 605 */ 606 function test_get_table_from_query( $query, $table ) { 607 $this->assertEquals( $table, self::$_wpdb->get_table_from_query( $query ) ); 608 } 609 610 function data_get_table_from_query_false() { 611 $table = 'a_test_table_name'; 612 return array( 613 array( "LOL THIS ISN'T EVEN A QUERY $table" ), 614 ); 615 } 616 617 /** 618 * @dataProvider data_get_table_from_query_false 619 * @ticket 21212 620 */ 621 function test_get_table_from_query_false( $query ) { 622 $this->assertFalse( self::$_wpdb->get_table_from_query( $query ) ); 623 } 624 625 /** 626 * @ticket 21212 627 */ 628 function data_process_field_formats() { 629 $core_db_fields_no_format_specified = array( 630 array( 'post_content' => 'foo', 'post_parent' => 0 ), 631 null, 632 array( 633 'post_content' => array( 'value' => 'foo', 'format' => '%s' ), 634 'post_parent' => array( 'value' => 0, 'format' => '%d' ), 635 ) 636 ); 637 638 $core_db_fields_formats_specified = array( 639 array( 'post_content' => 'foo', 'post_parent' => 0 ), 640 array( '%d', '%s' ), // These override core field_types 641 array( 642 'post_content' => array( 'value' => 'foo', 'format' => '%d' ), 643 'post_parent' => array( 'value' => 0, 'format' => '%s' ), 644 ) 645 ); 646 647 $misc_fields_no_format_specified = array( 648 array( 'this_is_not_a_core_field' => 'foo', 'this_is_not_either' => 0 ), 649 null, 650 array( 651 'this_is_not_a_core_field' => array( 'value' => 'foo', 'format' => '%s' ), 652 'this_is_not_either' => array( 'value' => 0, 'format' => '%s' ), 653 ) 654 ); 655 656 $misc_fields_formats_specified = array( 657 array( 'this_is_not_a_core_field' => 0, 'this_is_not_either' => 1.2 ), 658 array( '%d', '%f' ), 659 array( 660 'this_is_not_a_core_field' => array( 'value' => 0, 'format' => '%d' ), 661 'this_is_not_either' => array( 'value' => 1.2, 'format' => '%f' ), 662 ) 663 ); 664 665 $misc_fields_insufficient_formats_specified = array( 666 array( 'this_is_not_a_core_field' => 0, 'this_is_not_either' => 's', 'nor_this' => 1 ), 667 array( '%d', '%s' ), // The first format is used for the third 668 array( 669 'this_is_not_a_core_field' => array( 'value' => 0, 'format' => '%d' ), 670 'this_is_not_either' => array( 'value' => 's', 'format' => '%s' ), 671 'nor_this' => array( 'value' => 1, 'format' => '%d' ), 672 ) 673 ); 674 675 $vars = get_defined_vars(); 676 // Push the variable name onto the end for assertSame $message 677 foreach ( $vars as $var_name => $var ) { 678 $vars[ $var_name ][] = $var_name; 679 } 680 return array_values( $vars ); 681 } 682 683 /** 684 * @dataProvider data_process_field_formats 685 * @ticket 21212 686 */ 687 function test_process_field_formats( $data, $format, $expected, $message ) { 688 $actual = self::$_wpdb->process_field_formats( $data, $format ); 689 $this->assertSame( $expected, $actual, $message ); 690 } 691 692 /** 693 * @ticket 21212 694 */ 695 function test_process_fields() { 696 global $wpdb; 697 698 if ( $wpdb->charset ) { 699 $expected_charset = $wpdb->charset; 700 } else { 701 $expected_charset = $wpdb->get_col_charset( $wpdb->posts, 'post_content' ); 702 } 703 704 if ( ! in_array( $expected_charset, array( 'utf8', 'utf8mb4', 'latin1' ) ) ) { 705 $this->markTestSkipped( "This test only works with utf8, utf8mb4 or latin1 character sets" ); 706 } 707 708 $data = array( 'post_content' => '¡foo foo foo!' ); 709 $expected = array( 710 'post_content' => array( 711 'value' => '¡foo foo foo!', 712 'format' => '%s', 713 'charset' => $expected_charset, 714 'ascii' => false, 715 ) 716 ); 717 718 $this->assertSame( $expected, self::$_wpdb->process_fields( $wpdb->posts, $data, null ) ); 719 } 720 721 /** 722 * @ticket 21212 723 * @depends test_process_fields 724 */ 725 function test_process_fields_on_nonexistent_table( $data ) { 726 self::$_wpdb->suppress_errors( true ); 727 $data = array( 'post_content' => '¡foo foo foo!' ); 728 $this->assertFalse( self::$_wpdb->process_fields( 'nonexistent_table', $data, null ) ); 729 self::$_wpdb->suppress_errors( false ); 730 } 731 732 /** 733 * @ticket 21212 734 */ 735 function test_pre_get_table_charset_filter() { 736 add_filter( 'pre_get_table_charset', array( $this, 'filter_pre_get_table_charset' ), 10, 2 ); 737 $charset = self::$_wpdb->get_table_charset( 'some_table' ); 738 remove_filter( 'pre_get_table_charset', array( $this, 'filter_pre_get_table_charset' ), 10 ); 739 740 $this->assertEquals( $charset, 'fake_charset' ); 741 } 742 function filter_pre_get_table_charset( $charset, $table ) { 743 return 'fake_charset'; 744 } 745 746 /** 747 * @ticket 21212 748 */ 749 function test_pre_get_col_charset_filter() { 750 add_filter( 'pre_get_col_charset', array( $this, 'filter_pre_get_col_charset' ), 10, 3 ); 751 $charset = self::$_wpdb->get_col_charset( 'some_table', 'some_col' ); 752 remove_filter( 'pre_get_col_charset', array( $this, 'filter_pre_get_col_charset' ), 10 ); 753 754 $this->assertEquals( $charset, 'fake_col_charset' ); 755 } 756 function filter_pre_get_col_charset( $charset, $table, $column ) { 757 return 'fake_col_charset'; 758 } 461 759 } 760 -
branches/4.0/tests/phpunit/tests/post.php
r29351 r32181 993 993 994 994 } 995 996 /** 997 * @ticket 21212 998 */ 999 function test_utf8mb3_post_saves_with_emoji() { 1000 global $wpdb; 1001 $_wpdb = new wpdb_exposed_methods_for_testing(); 1002 1003 if ( 'utf8' !== $_wpdb->get_col_charset( $wpdb->posts, 'post_title' ) ) { 1004 $this->markTestSkipped( 'This test is only useful with the utf8 character set' ); 1005 } 1006 1007 require_once( ABSPATH . '/wp-admin/includes/post.php' ); 1008 1009 $post_id = $this->factory->post->create(); 1010 1011 $data = array( 1012 'post_ID' => $post_id, 1013 'post_title' => "foo\xf0\x9f\x98\x88bar", 1014 'post_content' => "foo\xf0\x9f\x98\x8ebaz", 1015 'post_excerpt' => "foo\xf0\x9f\x98\x90bat" 1016 ); 1017 1018 $expected = array( 1019 'post_title' => "foobar", 1020 'post_content' => "foobaz", 1021 'post_excerpt' => "foobat" 1022 ); 1023 1024 edit_post( $data ); 1025 1026 $post = get_post( $post_id ); 1027 1028 foreach( $expected as $field => $value ) { 1029 $this->assertEquals( $post->$field, $value ); 1030 } 1031 }
Note: See TracChangeset
for help on using the changeset viewer.