Changeset 32163 for branches/4.1/src/wp-includes/wp-db.php
- Timestamp:
- 04/20/2015 05:08:00 AM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/4.1/src/wp-includes/wp-db.php
r30807 r32163 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_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 { … … 1542 1587 public function query( $query ) { 1543 1588 if ( ! $this->ready ) { 1589 $this->check_current_query = true; 1544 1590 return false; 1545 1591 } … … 1562 1608 $this->func_call = "\$db->query(\"$query\")"; 1563 1609 1610 // If we're writing to the database, make sure the query will write safely. 1611 if ( $this->check_current_query && ! $this->check_ascii( $query ) ) { 1612 $stripped_query = $this->strip_invalid_text_from_query( $query ); 1613 // strip_invalid_text_from_query() can perform queries, so we need 1614 // to flush again, just to make sure everything is clear. 1615 $this->flush(); 1616 if ( $stripped_query !== $query ) { 1617 $this->insert_id = 0; 1618 return false; 1619 } 1620 } 1621 1622 $this->check_current_query = true; 1564 1623 // Keep track of the last query for debug.. 1565 1624 $this->last_query = $query; … … 1731 1790 */ 1732 1791 function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) { 1733 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) 1792 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) { 1734 1793 return false; 1794 } 1795 1796 $data = $this->process_fields( $table, $data, $format ); 1797 if ( false === $data ) { 1798 return false; 1799 } 1800 1801 $formats = $values = array(); 1802 foreach ( $data as $value ) { 1803 $formats[] = $value['format']; 1804 $values[] = $value['value']; 1805 } 1806 1807 $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`'; 1808 $formats = implode( ', ', $formats ); 1809 1810 $sql = "$type INTO `$table` ($fields) VALUES ($formats)"; 1811 1735 1812 $this->insert_id = 0; 1736 $formats = $format = (array) $format; 1737 $fields = array_keys( $data ); 1738 $formatted_fields = array(); 1739 foreach ( $fields 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 $formatted_fields[] = $form; 1747 } 1748 $sql = "{$type} INTO `$table` (`" . implode( '`,`', $fields ) . "`) VALUES (" . implode( ",", $formatted_fields ) . ")"; 1749 return $this->query( $this->prepare( $sql, $data ) ); 1813 $this->check_current_query = false; 1814 return $this->query( $this->prepare( $sql, $values ) ); 1750 1815 } 1751 1816 … … 1770 1835 */ 1771 1836 public function update( $table, $data, $where, $format = null, $where_format = null ) { 1772 if ( ! is_array( $data ) || ! is_array( $where ) ) 1837 if ( ! is_array( $data ) || ! is_array( $where ) ) { 1773 1838 return false; 1774 1775 $formats = $format = (array) $format; 1776 $bits = $wheres = array(); 1777 foreach ( (array) array_keys( $data ) as $field ) { 1778 if ( !empty( $format ) ) 1779 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0]; 1780 elseif ( isset($this->field_types[$field]) ) 1781 $form = $this->field_types[$field]; 1782 else 1783 $form = '%s'; 1784 $bits[] = "`$field` = {$form}"; 1785 } 1786 1787 $where_formats = $where_format = (array) $where_format; 1788 foreach ( (array) array_keys( $where ) as $field ) { 1789 if ( !empty( $where_format ) ) 1790 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0]; 1791 elseif ( isset( $this->field_types[$field] ) ) 1792 $form = $this->field_types[$field]; 1793 else 1794 $form = '%s'; 1795 $wheres[] = "`$field` = {$form}"; 1796 } 1797 1798 $sql = "UPDATE `$table` SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres ); 1799 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 ) ); 1800 1867 } 1801 1868 … … 1817 1884 */ 1818 1885 public function delete( $table, $where, $where_format = null ) { 1819 if ( ! is_array( $where ) ) 1886 if ( ! is_array( $where ) ) { 1820 1887 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]; 1888 } 1889 1890 $where = $this->process_fields( $table, $where, $where_format ); 1891 if ( false === $where ) { 1892 return false; 1893 } 1894 1895 $conditions = $values = array(); 1896 foreach ( $where as $field => $value ) { 1897 $conditions[] = "`$field` = " . $value['format']; 1898 $values[] = $value['value']; 1899 } 1900 1901 $conditions = implode( ' AND ', $conditions ); 1902 1903 $sql = "DELETE FROM `$table` WHERE $conditions"; 1904 1905 $this->check_current_query = false; 1906 return $this->query( $this->prepare( $sql, $values ) ); 1907 } 1908 1909 /** 1910 * Processes arrays of field/value pairs and field formats. 1911 * 1912 * This is a helper method for wpdb's CRUD methods, which take field/value 1913 * pairs for inserts, updates, and where clauses. This method first pairs 1914 * each value with a format. Then it determines the charset of that field, 1915 * using that to determine if any invalid text would be stripped. If text is 1916 * stripped, then field processing is rejected and the query fails. 1917 * 1918 * @since 4.2.0 1919 * @access protected 1920 * 1921 * @param string $table Table name. 1922 * @param array $data Field/value pair. 1923 * @param mixed $format Format for each field. 1924 * @return array|bool Returns an array of fields that contain paired values 1925 * and formats. Returns false for invalid values. 1926 */ 1927 protected function process_fields( $table, $data, $format ) { 1928 $data = $this->process_field_formats( $data, $format ); 1929 $data = $this->process_field_charsets( $data, $table ); 1930 if ( false === $data ) { 1931 return false; 1932 } 1933 1934 $converted_data = $this->strip_invalid_text( $data ); 1935 1936 if ( $data !== $converted_data ) { 1937 return false; 1938 } 1939 1940 return $data; 1941 } 1942 1943 /** 1944 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods. 1945 * 1946 * @since 4.2.0 1947 * @access protected 1948 * 1949 * @param array $data Array of fields to values. 1950 * @param mixed $format Formats to be mapped to the values in $data. 1951 * @return array Array, keyed by field names with values being an array 1952 * of 'value' and 'format' keys. 1953 */ 1954 protected function process_field_formats( $data, $format ) { 1955 $formats = $original_formats = (array) $format; 1956 1957 foreach ( $data as $field => $value ) { 1958 $value = array( 1959 'value' => $value, 1960 'format' => '%s', 1961 ); 1962 1963 if ( ! empty( $format ) ) { 1964 $value['format'] = array_shift( $formats ); 1965 if ( ! $value['format'] ) { 1966 $value['format'] = reset( $original_formats ); 1967 } 1829 1968 } elseif ( isset( $this->field_types[ $field ] ) ) { 1830 $form = $this->field_types[ $field ]; 1969 $value['format'] = $this->field_types[ $field ]; 1970 } 1971 1972 $data[ $field ] = $value; 1973 } 1974 1975 return $data; 1976 } 1977 1978 /** 1979 * Adds field charsets to field/value/format arrays generated by 1980 * the {@see wpdb::process_field_formats()} method. 1981 * 1982 * @since 4.2.0 1983 * @access protected 1984 * 1985 * @param array $data As it comes from the {@see wpdb::process_field_formats()} method. 1986 * @param string $table Table name. 1987 * @return The same array as $data with additional 'charset' keys. 1988 */ 1989 protected function process_field_charsets( $data, $table ) { 1990 foreach ( $data as $field => $value ) { 1991 if ( '%d' === $value['format'] || '%f' === $value['format'] ) { 1992 // We can skip this field if we know it isn't a string. 1993 // This checks %d/%f versus ! %s because it's sprintf() could take more. 1994 $value['charset'] = false; 1995 } elseif ( $this->check_ascii( $value['value'] ) ) { 1996 // If it's ASCII, then we don't need the charset. We can skip this field. 1997 $value['charset'] = false; 1831 1998 } 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 ) ); 1999 $value['charset'] = $this->get_col_charset( $table, $field ); 2000 if ( is_wp_error( $value['charset'] ) ) { 2001 return false; 2002 } 2003 2004 // This isn't ASCII. Don't have strip_invalid_text() re-check. 2005 $value['ascii'] = false; 2006 } 2007 2008 $data[ $field ] = $value; 2009 } 2010 2011 return $data; 1840 2012 } 1841 2013 … … 1856 2028 public function get_var( $query = null, $x = 0, $y = 0 ) { 1857 2029 $this->func_call = "\$db->get_var(\"$query\", $x, $y)"; 2030 2031 if ( $this->check_collation( $query ) ) { 2032 $this->check_current_query = false; 2033 } 1858 2034 1859 2035 if ( $query ) { … … 1885 2061 public function get_row( $query = null, $output = OBJECT, $y = 0 ) { 1886 2062 $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; 2063 2064 if ( $this->check_collation( $query ) ) { 2065 $this->check_current_query = false; 2066 } 2067 1887 2068 if ( $query ) { 1888 2069 $this->query( $query ); … … 1922 2103 */ 1923 2104 public function get_col( $query = null , $x = 0 ) { 2105 if ( $this->check_collation( $query ) ) { 2106 $this->check_current_query = false; 2107 } 2108 1924 2109 if ( $query ) { 1925 2110 $this->query( $query ); … … 1949 2134 public function get_results( $query = null, $output = OBJECT ) { 1950 2135 $this->func_call = "\$db->get_results(\"$query\", $output)"; 2136 2137 if ( $this->check_collation( $query ) ) { 2138 $this->check_current_query = false; 2139 } 1951 2140 1952 2141 if ( $query ) { … … 1992 2181 1993 2182 /** 2183 * Retrieves the character set for the given table. 2184 * 2185 * @since 4.2.0 2186 * @access protected 2187 * 2188 * @param string $table Table name. 2189 * @return string|WP_Error Table character set, {@see WP_Error} object if it couldn't be found. 2190 */ 2191 protected function get_table_charset( $table ) { 2192 $tablekey = strtolower( $table ); 2193 2194 /** 2195 * Filter the table charset value before the DB is checked. 2196 * 2197 * Passing a non-null value to the filter will effectively short-circuit 2198 * checking the DB for the charset, returning that value instead. 2199 * 2200 * @since 4.2.0 2201 * 2202 * @param string $charset The character set to use. Default null. 2203 * @param string $table The name of the table being checked. 2204 */ 2205 $charset = apply_filters( 'pre_get_table_charset', null, $table ); 2206 if ( null !== $charset ) { 2207 return $charset; 2208 } 2209 2210 if ( isset( $this->table_charset[ $tablekey ] ) ) { 2211 return $this->table_charset[ $tablekey ]; 2212 } 2213 2214 $charsets = $columns = array(); 2215 $results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" ); 2216 if ( ! $results ) { 2217 return new WP_Error( 'wpdb_get_table_charset_failure' ); 2218 } 2219 2220 foreach ( $results as $column ) { 2221 $columns[ strtolower( $column->Field ) ] = $column; 2222 } 2223 2224 $this->col_meta[ $tablekey ] = $columns; 2225 2226 foreach ( $columns as $column ) { 2227 if ( ! empty( $column->Collation ) ) { 2228 list( $charset ) = explode( '_', $column->Collation ); 2229 $charsets[ strtolower( $charset ) ] = true; 2230 } 2231 2232 list( $type ) = explode( '(', $column->Type ); 2233 2234 // A binary/blob means the whole query gets treated like this. 2235 if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) { 2236 $this->table_charset[ $tablekey ] = 'binary'; 2237 return 'binary'; 2238 } 2239 } 2240 2241 // utf8mb3 is an alias for utf8. 2242 if ( isset( $charsets['utf8mb3'] ) ) { 2243 $charsets['utf8'] = true; 2244 unset( $charsets['utf8mb3'] ); 2245 } 2246 2247 // Check if we have more than one charset in play. 2248 $count = count( $charsets ); 2249 if ( 1 === $count ) { 2250 $charset = key( $charsets ); 2251 } elseif ( 0 === $count ) { 2252 // No charsets, assume this table can store whatever. 2253 $charset = false; 2254 } else { 2255 // More than one charset. Remove latin1 if present and recalculate. 2256 unset( $charsets['latin1'] ); 2257 $count = count( $charsets ); 2258 if ( 1 === $count ) { 2259 // Only one charset (besides latin1). 2260 $charset = key( $charsets ); 2261 } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) { 2262 // Two charsets, but they're utf8 and utf8mb4, use utf8. 2263 $charset = 'utf8'; 2264 } else { 2265 // Two mixed character sets. ascii. 2266 $charset = 'ascii'; 2267 } 2268 } 2269 2270 $this->table_charset[ $tablekey ] = $charset; 2271 return $charset; 2272 } 2273 2274 /** 2275 * Retrieves the character set for the given column. 2276 * 2277 * @since 4.2.0 2278 * @access public 2279 * 2280 * @param string $table Table name. 2281 * @param string $column Column name. 2282 * @return mixed Column character set as a string. False if the column has no 2283 * character set. {@see WP_Error} object if there was an error. 2284 */ 2285 public function get_col_charset( $table, $column ) { 2286 $tablekey = strtolower( $table ); 2287 $columnkey = strtolower( $column ); 2288 2289 /** 2290 * Filter the column charset value before the DB is checked. 2291 * 2292 * Passing a non-null value to the filter will short-circuit 2293 * checking the DB for the charset, returning that value instead. 2294 * 2295 * @since 4.2.0 2296 * 2297 * @param string $charset The character set to use. Default null. 2298 * @param string $table The name of the table being checked. 2299 * @param string $column The name of the column being checked. 2300 */ 2301 $charset = apply_filters( 'pre_get_col_charset', null, $table, $column ); 2302 if ( null !== $charset ) { 2303 return $charset; 2304 } 2305 2306 // Skip this entirely if this isn't a MySQL database. 2307 if ( false === $this->is_mysql ) { 2308 return false; 2309 } 2310 2311 if ( empty( $this->table_charset[ $tablekey ] ) ) { 2312 // This primes column information for us. 2313 $table_charset = $this->get_table_charset( $table ); 2314 if ( is_wp_error( $table_charset ) ) { 2315 return $table_charset; 2316 } 2317 } 2318 2319 // If still no column information, return the table charset. 2320 if ( empty( $this->col_meta[ $tablekey ] ) ) { 2321 return $this->table_charset[ $tablekey ]; 2322 } 2323 2324 // If this column doesn't exist, return the table charset. 2325 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { 2326 return $this->table_charset[ $tablekey ]; 2327 } 2328 2329 // Return false when it's not a string column. 2330 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) { 2331 return false; 2332 } 2333 2334 list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation ); 2335 return $charset; 2336 } 2337 2338 /** 2339 * Check if a string is ASCII. 2340 * 2341 * The negative regex is faster for non-ASCII strings, as it allows 2342 * the search to finish as soon as it encounters a non-ASCII character. 2343 * 2344 * @since 4.2.0 2345 * @access protected 2346 * 2347 * @param string $string String to check. 2348 * @return bool True if ASCII, false if not. 2349 */ 2350 protected function check_ascii( $string ) { 2351 if ( function_exists( 'mb_check_encoding' ) ) { 2352 if ( mb_check_encoding( $string, 'ASCII' ) ) { 2353 return true; 2354 } 2355 } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) { 2356 return true; 2357 } 2358 2359 return false; 2360 } 2361 2362 /** 2363 * Check if the query is accessing a collation considered safe on the current version of MySQL. 2364 * 2365 * @since 4.2.0 2366 * @access protected 2367 * 2368 * @param string $query The query to check. 2369 * @return bool True if the collation is safe, false if it isn't. 2370 */ 2371 protected function check_collation( $query ) { 2372 if ( $this->checking_collation ) { 2373 return true; 2374 } 2375 $table = $this->get_table_from_query( $query ); 2376 if ( ! $table ) { 2377 return false; 2378 } 2379 2380 $this->checking_collation = true; 2381 $this->get_table_charset( $table ); 2382 $this->checking_collation = false; 2383 2384 $table = strtolower( $table ); 2385 if ( empty( $this->col_meta[ $table ] ) ) { 2386 return false; 2387 } 2388 2389 foreach( $this->col_meta[ $table ] as $col ) { 2390 if ( empty( $col->Collation ) ) { 2391 continue; 2392 } 2393 2394 if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) { 2395 return false; 2396 } 2397 } 2398 2399 return true; 2400 } 2401 2402 /** 2403 * Strips any invalid characters based on value/charset pairs. 2404 * 2405 * @since 4.2.0 2406 * @access protected 2407 * 2408 * @param array $data Array of value arrays. Each value array has the keys 2409 * 'value' and 'charset'. An optional 'ascii' key can be 2410 * set to false to avoid redundant ASCII checks. 2411 * @return array|WP_Error The $data parameter, with invalid characters removed from 2412 * each value. This works as a passthrough: any additional keys 2413 * such as 'field' are retained in each value array. If we cannot 2414 * remove invalid characters, a {@see WP_Error} object is returned. 2415 */ 2416 protected function strip_invalid_text( $data ) { 2417 // Some multibyte character sets that we can check in PHP. 2418 $mb_charsets = array( 2419 'ascii' => 'ASCII', 2420 'big5' => 'BIG-5', 2421 'eucjpms' => 'eucJP-win', 2422 'gb2312' => 'EUC-CN', 2423 'ujis' => 'EUC-JP', 2424 'utf32' => 'UTF-32', 2425 ); 2426 2427 $supported_charsets = array(); 2428 if ( function_exists( 'mb_list_encodings' ) ) { 2429 $supported_charsets = mb_list_encodings(); 2430 } 2431 2432 $db_check_string = false; 2433 2434 foreach ( $data as &$value ) { 2435 $charset = $value['charset']; 2436 2437 // Column isn't a string, or is latin1, which will will happily store anything. 2438 if ( false === $charset || 'latin1' === $charset ) { 2439 continue; 2440 } 2441 2442 if ( ! is_string( $value['value'] ) ) { 2443 continue; 2444 } 2445 2446 // ASCII is always OK. 2447 if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) { 2448 continue; 2449 } 2450 2451 // Convert the text locally. 2452 if ( $supported_charsets ) { 2453 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) { 2454 $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] ); 2455 continue; 2456 } 2457 } 2458 2459 // utf8 can be handled by regex, which is a bunch faster than a DB lookup. 2460 if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) { 2461 $regex = '/ 2462 ( 2463 (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx 2464 | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx 2465 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 2466 | [\xE1-\xEC][\x80-\xBF]{2} 2467 | \xED[\x80-\x9F][\x80-\xBF] 2468 | [\xEE-\xEF][\x80-\xBF]{2}'; 2469 2470 if ( 'utf8mb4' === $charset) { 2471 $regex .= ' 2472 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 2473 | [\xF1-\xF3][\x80-\xBF]{3} 2474 | \xF4[\x80-\x8F][\x80-\xBF]{2} 2475 '; 2476 } 2477 2478 $regex .= '){1,50} # ...one or more times 2479 ) 2480 | . # anything else 2481 /x'; 2482 $value['value'] = preg_replace( $regex, '$1', $value['value'] ); 2483 continue; 2484 } 2485 2486 // We couldn't use any local conversions, send it to the DB. 2487 $value['db'] = $db_check_string = true; 2488 } 2489 unset( $value ); // Remove by reference. 2490 2491 if ( $db_check_string ) { 2492 $queries = array(); 2493 foreach ( $data as $col => $value ) { 2494 if ( ! empty( $value['db'] ) ) { 2495 if ( ! isset( $queries[ $value['charset'] ] ) ) { 2496 $queries[ $value['charset'] ] = array(); 2497 } 2498 2499 // Split the CONVERT() calls by charset, so we can make sure the connection is right 2500 $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] ); 2501 } 2502 } 2503 2504 $connection_charset = $this->charset; 2505 foreach ( $queries as $charset => $query ) { 2506 if ( ! $query ) { 2507 continue; 2508 } 2509 2510 // Change the charset to match the string(s) we're converting 2511 if ( $charset !== $connection_charset ) { 2512 $connection_charset = $charset; 2513 $this->set_charset( $this->dbh, $charset ); 2514 } 2515 2516 $this->check_current_query = false; 2517 2518 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N ); 2519 if ( ! $row ) { 2520 $this->set_charset( $this->dbh, $connection_charset ); 2521 return new WP_Error( 'wpdb_strip_invalid_text_failure' ); 2522 } 2523 2524 $cols = array_keys( $query ); 2525 $col_count = count( $cols ); 2526 for ( $ii = 0; $ii < $col_count; $ii++ ) { 2527 $data[ $cols[ $ii ] ]['value'] = $row[ $ii ]; 2528 } 2529 } 2530 2531 // Don't forget to change the charset back! 2532 if ( $connection_charset !== $this->charset ) { 2533 $this->set_charset( $this->dbh ); 2534 } 2535 } 2536 2537 return $data; 2538 } 2539 2540 /** 2541 * Strips any invalid characters from the query. 2542 * 2543 * @since 4.2.0 2544 * @access protected 2545 * 2546 * @param string $query Query to convert. 2547 * @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails. 2548 */ 2549 protected function strip_invalid_text_from_query( $query ) { 2550 $table = $this->get_table_from_query( $query ); 2551 if ( $table ) { 2552 $charset = $this->get_table_charset( $table ); 2553 if ( is_wp_error( $charset ) ) { 2554 return $charset; 2555 } 2556 2557 // We can't reliably strip text from tables containing binary/blob columns 2558 if ( 'binary' === $charset ) { 2559 return $query; 2560 } 2561 } else { 2562 $charset = $this->charset; 2563 } 2564 2565 $data = array( 2566 'value' => $query, 2567 'charset' => $charset, 2568 'ascii' => false, 2569 ); 2570 2571 $data = $this->strip_invalid_text( array( $data ) ); 2572 if ( is_wp_error( $data ) ) { 2573 return $data; 2574 } 2575 2576 return $data[0]['value']; 2577 } 2578 2579 /** 2580 * Strips any invalid characters from the string for a given table and column. 2581 * 2582 * @since 4.2.0 2583 * @access public 2584 * 2585 * @param string $table Table name. 2586 * @param string $column Column name. 2587 * @param string $value The text to check. 2588 * @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails. 2589 */ 2590 public function strip_invalid_text_for_column( $table, $column, $value ) { 2591 if ( ! is_string( $value ) || $this->check_ascii( $value ) ) { 2592 return $value; 2593 } 2594 2595 $charset = $this->get_col_charset( $table, $column ); 2596 if ( ! $charset ) { 2597 // Not a string column. 2598 return $value; 2599 } elseif ( is_wp_error( $charset ) ) { 2600 // Bail on real errors. 2601 return $charset; 2602 } 2603 2604 $data = array( 2605 $column => array( 2606 'value' => $value, 2607 'charset' => $charset, 2608 'ascii' => false, 2609 ) 2610 ); 2611 2612 $data = $this->strip_invalid_text( $data ); 2613 if ( is_wp_error( $data ) ) { 2614 return $data; 2615 } 2616 2617 return $data[ $column ]['value']; 2618 } 2619 2620 /** 2621 * Find the first table name referenced in a query. 2622 * 2623 * @since 4.2.0 2624 * @access protected 2625 * 2626 * @param string $query The query to search. 2627 * @return string|false $table The table name found, or false if a table couldn't be found. 2628 */ 2629 protected function get_table_from_query( $query ) { 2630 // Remove characters that can legally trail the table name. 2631 $query = rtrim( $query, ';/-#' ); 2632 2633 // Allow (select...) union [...] style queries. Use the first query's table name. 2634 $query = ltrim( $query, "\r\n\t (" ); 2635 2636 /* 2637 * Strip everything between parentheses except nested selects and use only 1,000 2638 * chars of the query. 2639 */ 2640 $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', substr( $query, 0, 1000 ) ); 2641 2642 // Quickly match most common queries. 2643 if ( preg_match( '/^\s*(?:' 2644 . 'SELECT.*?\s+FROM' 2645 . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?' 2646 . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?' 2647 . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?' 2648 . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?' 2649 . ')\s+`?([\w-]+)`?/is', $query, $maybe ) ) { 2650 return $maybe[1]; 2651 } 2652 2653 // SHOW TABLE STATUS and SHOW TABLES 2654 if ( preg_match( '/^\s*(?:' 2655 . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' 2656 . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' 2657 . ')\W([\w-]+)\W/is', $query, $maybe ) ) { 2658 return $maybe[1]; 2659 } 2660 2661 // Big pattern for the rest of the table-related queries. 2662 if ( preg_match( '/^\s*(?:' 2663 . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM' 2664 . '|DESCRIBE|DESC|EXPLAIN|HANDLER' 2665 . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?' 2666 . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE' 2667 . '|TRUNCATE(?:\s+TABLE)?' 2668 . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?' 2669 . '|ALTER(?:\s+IGNORE)?\s+TABLE' 2670 . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?' 2671 . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON' 2672 . '|DROP\s+INDEX.*\s+ON' 2673 . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE' 2674 . '|(?:GRANT|REVOKE).*ON\s+TABLE' 2675 . '|SHOW\s+(?:.*FROM|.*TABLE)' 2676 . ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) { 2677 return $maybe[1]; 2678 } 2679 2680 return false; 2681 } 2682 2683 /** 1994 2684 * Load the column metadata from the last query. 1995 2685 *
Note: See TracChangeset
for help on using the changeset viewer.