Changeset 32181 for branches/4.0/src/wp-includes/wp-db.php
- Timestamp:
- 04/20/2015 10:53:33 AM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
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.
Note: See TracChangeset
for help on using the changeset viewer.