Changeset 32186 for branches/3.8/src/wp-includes/wp-db.php
- Timestamp:
- 04/20/2015 11:31:22 AM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/3.8/src/wp-includes/wp-db.php
r26512 r32186 141 141 142 142 /** 143 * Cached column info, for sanity checking data before inserting 144 * 145 * @since 4.2.0 146 * @access protected 147 * @var array 148 */ 149 protected $col_meta = array(); 150 151 /** 152 * Calculated character sets on tables 153 * 154 * @since 4.2.0 155 * @access protected 156 * @var array 157 */ 158 protected $table_charset = array(); 159 160 /** 161 * Whether text fields in the current query need to be sanity checked. 162 * 163 * @since 4.2.0 164 * @access protected 165 * @var bool 166 */ 167 protected $check_current_query = true; 168 169 /** 170 * Flag to ensure we don't run into recursion problems when checking the collation. 171 * 172 * @since 4.2.0 173 * @access private 174 * @see wpdb::check_safe_collation() 175 * @var boolean 176 */ 177 private $checking_collation = false; 178 179 /** 143 180 * Saved info on the table column 144 181 * … … 576 613 */ 577 614 function __set( $name, $value ) { 615 $protected_members = array( 616 'col_meta', 617 'table_charset', 618 'check_current_query', 619 ); 620 if ( in_array( $name, $protected_members, true ) ) { 621 return; 622 } 578 623 $this->$name = $value; 579 624 } … … 1187 1232 */ 1188 1233 function query( $query ) { 1189 if ( ! $this->ready ) 1234 if ( ! $this->ready ) { 1235 $this->check_current_query = true; 1190 1236 return false; 1237 } 1238 1191 1239 /** 1192 1240 * Filter the database query. … … 1205 1253 $this->func_call = "\$db->query(\"$query\")"; 1206 1254 1255 // If we're writing to the database, make sure the query will write safely. 1256 if ( $this->check_current_query && ! $this->check_ascii( $query ) ) { 1257 $stripped_query = $this->strip_invalid_text_from_query( $query ); 1258 // strip_invalid_text_from_query() can perform queries, so we need 1259 // to flush again, just to make sure everything is clear. 1260 $this->flush(); 1261 if ( $stripped_query !== $query ) { 1262 $this->insert_id = 0; 1263 return false; 1264 } 1265 } 1266 1267 $this->check_current_query = true; 1268 1207 1269 // Keep track of the last query for debug.. 1208 1270 $this->last_query = $query; … … 1318 1380 */ 1319 1381 function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) { 1320 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) 1382 if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) { 1321 1383 return false; 1384 } 1385 1386 $data = $this->process_fields( $table, $data, $format ); 1387 if ( false === $data ) { 1388 return false; 1389 } 1390 1391 $formats = $values = array(); 1392 foreach ( $data as $value ) { 1393 $formats[] = $value['format']; 1394 $values[] = $value['value']; 1395 } 1396 1397 $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`'; 1398 $formats = implode( ', ', $formats ); 1399 1400 $sql = "$type INTO `$table` ($fields) VALUES ($formats)"; 1401 1322 1402 $this->insert_id = 0; 1323 $formats = $format = (array) $format; 1324 $fields = array_keys( $data ); 1325 $formatted_fields = array(); 1326 foreach ( $fields as $field ) { 1327 if ( !empty( $format ) ) 1328 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0]; 1329 elseif ( isset( $this->field_types[$field] ) ) 1330 $form = $this->field_types[$field]; 1331 else 1332 $form = '%s'; 1333 $formatted_fields[] = $form; 1334 } 1335 $sql = "{$type} INTO `$table` (`" . implode( '`,`', $fields ) . "`) VALUES (" . implode( ",", $formatted_fields ) . ")"; 1336 return $this->query( $this->prepare( $sql, $data ) ); 1403 $this->check_current_query = false; 1404 return $this->query( $this->prepare( $sql, $values ) ); 1337 1405 } 1338 1406 … … 1359 1427 */ 1360 1428 function update( $table, $data, $where, $format = null, $where_format = null ) { 1361 if ( ! is_array( $data ) || ! is_array( $where ) ) 1429 if ( ! is_array( $data ) || ! is_array( $where ) ) { 1362 1430 return false; 1363 1364 $formats = $format = (array) $format; 1365 $bits = $wheres = array(); 1366 foreach ( (array) array_keys( $data ) as $field ) { 1367 if ( !empty( $format ) ) 1368 $form = ( $form = array_shift( $formats ) ) ? $form : $format[0]; 1369 elseif ( isset($this->field_types[$field]) ) 1370 $form = $this->field_types[$field]; 1371 else 1372 $form = '%s'; 1373 $bits[] = "`$field` = {$form}"; 1374 } 1375 1376 $where_formats = $where_format = (array) $where_format; 1377 foreach ( (array) array_keys( $where ) as $field ) { 1378 if ( !empty( $where_format ) ) 1379 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0]; 1380 elseif ( isset( $this->field_types[$field] ) ) 1381 $form = $this->field_types[$field]; 1382 else 1383 $form = '%s'; 1384 $wheres[] = "`$field` = {$form}"; 1385 } 1386 1387 $sql = "UPDATE `$table` SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres ); 1388 return $this->query( $this->prepare( $sql, array_merge( array_values( $data ), array_values( $where ) ) ) ); 1431 } 1432 1433 $data = $this->process_fields( $table, $data, $format ); 1434 if ( false === $data ) { 1435 return false; 1436 } 1437 $where = $this->process_fields( $table, $where, $where_format ); 1438 if ( false === $where ) { 1439 return false; 1440 } 1441 1442 $fields = $conditions = $values = array(); 1443 foreach ( $data as $field => $value ) { 1444 $fields[] = "`$field` = " . $value['format']; 1445 $values[] = $value['value']; 1446 } 1447 foreach ( $where as $field => $value ) { 1448 $conditions[] = "`$field` = " . $value['format']; 1449 $values[] = $value['value']; 1450 } 1451 1452 $fields = implode( ', ', $fields ); 1453 $conditions = implode( ' AND ', $conditions ); 1454 1455 $sql = "UPDATE `$table` SET $fields WHERE $conditions"; 1456 1457 $this->check_current_query = false; 1458 return $this->query( $this->prepare( $sql, $values ) ); 1389 1459 } 1390 1460 … … 1408 1478 */ 1409 1479 function delete( $table, $where, $where_format = null ) { 1410 if ( ! is_array( $where ) ) 1480 if ( ! is_array( $where ) ) { 1411 1481 return false; 1412 1413 $bits = $wheres = array(); 1414 1415 $where_formats = $where_format = (array) $where_format; 1416 1417 foreach ( array_keys( $where ) as $field ) { 1418 if ( !empty( $where_format ) ) { 1419 $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0]; 1482 } 1483 1484 $where = $this->process_fields( $table, $where, $where_format ); 1485 if ( false === $where ) { 1486 return false; 1487 } 1488 1489 $conditions = $values = array(); 1490 foreach ( $where as $field => $value ) { 1491 $conditions[] = "`$field` = " . $value['format']; 1492 $values[] = $value['value']; 1493 } 1494 1495 $conditions = implode( ' AND ', $conditions ); 1496 1497 $sql = "DELETE FROM `$table` WHERE $conditions"; 1498 1499 $this->check_current_query = false; 1500 return $this->query( $this->prepare( $sql, $values ) ); 1501 } 1502 1503 1504 /** 1505 * Processes arrays of field/value pairs and field formats. 1506 * 1507 * This is a helper method for wpdb's CRUD methods, which take field/value 1508 * pairs for inserts, updates, and where clauses. This method first pairs 1509 * each value with a format. Then it determines the charset of that field, 1510 * using that to determine if any invalid text would be stripped. If text is 1511 * stripped, then field processing is rejected and the query fails. 1512 * 1513 * @since 4.2.0 1514 * @access protected 1515 * 1516 * @param string $table Table name. 1517 * @param array $data Field/value pair. 1518 * @param mixed $format Format for each field. 1519 * @return array|bool Returns an array of fields that contain paired values 1520 * and formats. Returns false for invalid values. 1521 */ 1522 protected function process_fields( $table, $data, $format ) { 1523 $data = $this->process_field_formats( $data, $format ); 1524 $data = $this->process_field_charsets( $data, $table ); 1525 if ( false === $data ) { 1526 return false; 1527 } 1528 1529 $converted_data = $this->strip_invalid_text( $data ); 1530 1531 if ( $data !== $converted_data ) { 1532 return false; 1533 } 1534 1535 return $data; 1536 } 1537 1538 /** 1539 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods. 1540 * 1541 * @since 4.2.0 1542 * @access protected 1543 * 1544 * @param array $data Array of fields to values. 1545 * @param mixed $format Formats to be mapped to the values in $data. 1546 * @return array Array, keyed by field names with values being an array 1547 * of 'value' and 'format' keys. 1548 */ 1549 protected function process_field_formats( $data, $format ) { 1550 $formats = $original_formats = (array) $format; 1551 1552 foreach ( $data as $field => $value ) { 1553 $value = array( 1554 'value' => $value, 1555 'format' => '%s', 1556 ); 1557 1558 if ( ! empty( $format ) ) { 1559 $value['format'] = array_shift( $formats ); 1560 if ( ! $value['format'] ) { 1561 $value['format'] = reset( $original_formats ); 1562 } 1420 1563 } elseif ( isset( $this->field_types[ $field ] ) ) { 1421 $form = $this->field_types[ $field ]; 1564 $value['format'] = $this->field_types[ $field ]; 1565 } 1566 1567 $data[ $field ] = $value; 1568 } 1569 1570 return $data; 1571 } 1572 1573 /** 1574 * Adds field charsets to field/value/format arrays generated by 1575 * the {@see wpdb::process_field_formats()} method. 1576 * 1577 * @since 4.2.0 1578 * @access protected 1579 * 1580 * @param array $data As it comes from the {@see wpdb::process_field_formats()} method. 1581 * @param string $table Table name. 1582 * @return The same array as $data with additional 'charset' keys. 1583 */ 1584 protected function process_field_charsets( $data, $table ) { 1585 foreach ( $data as $field => $value ) { 1586 if ( '%d' === $value['format'] || '%f' === $value['format'] ) { 1587 // We can skip this field if we know it isn't a string. 1588 // This checks %d/%f versus ! %s because it's sprintf() could take more. 1589 $value['charset'] = false; 1590 } elseif ( $this->check_ascii( $value['value'] ) ) { 1591 // If it's ASCII, then we don't need the charset. We can skip this field. 1592 $value['charset'] = false; 1422 1593 } else { 1423 $form = '%s'; 1424 } 1425 1426 $wheres[] = "$field = $form"; 1427 } 1428 1429 $sql = "DELETE FROM $table WHERE " . implode( ' AND ', $wheres ); 1430 return $this->query( $this->prepare( $sql, $where ) ); 1431 } 1432 1594 $value['charset'] = $this->get_col_charset( $table, $field ); 1595 if ( is_wp_error( $value['charset'] ) ) { 1596 return false; 1597 } 1598 1599 // This isn't ASCII. Don't have strip_invalid_text() re-check. 1600 $value['ascii'] = false; 1601 } 1602 1603 $data[ $field ] = $value; 1604 } 1605 1606 return $data; 1607 } 1433 1608 1434 1609 /** … … 1448 1623 function get_var( $query = null, $x = 0, $y = 0 ) { 1449 1624 $this->func_call = "\$db->get_var(\"$query\", $x, $y)"; 1625 1626 if ( $this->check_safe_collation( $query ) ) { 1627 $this->check_current_query = false; 1628 } 1629 1450 1630 if ( $query ) 1451 1631 $this->query( $query ); … … 1475 1655 function get_row( $query = null, $output = OBJECT, $y = 0 ) { 1476 1656 $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; 1657 1658 if ( $this->check_safe_collation( $query ) ) { 1659 $this->check_current_query = false; 1660 } 1661 1477 1662 if ( $query ) 1478 1663 $this->query( $query ); … … 1508 1693 */ 1509 1694 function get_col( $query = null , $x = 0 ) { 1695 if ( $this->check_safe_collation( $query ) ) { 1696 $this->check_current_query = false; 1697 } 1698 1510 1699 if ( $query ) 1511 1700 $this->query( $query ); … … 1534 1723 function get_results( $query = null, $output = OBJECT ) { 1535 1724 $this->func_call = "\$db->get_results(\"$query\", $output)"; 1725 1726 if ( $this->check_safe_collation( $query ) ) { 1727 $this->check_current_query = false; 1728 } 1536 1729 1537 1730 if ( $query ) … … 1572 1765 } 1573 1766 1767 1768 /** 1769 * Retrieves the character set for the given table. 1770 * 1771 * @since 4.2.0 1772 * @access protected 1773 * 1774 * @param string $table Table name. 1775 * @return string|WP_Error Table character set, {@see WP_Error} object if it couldn't be found. 1776 */ 1777 protected function get_table_charset( $table ) { 1778 $tablekey = strtolower( $table ); 1779 1780 /** 1781 * Filter the table charset value before the DB is checked. 1782 * 1783 * Passing a non-null value to the filter will effectively short-circuit 1784 * checking the DB for the charset, returning that value instead. 1785 * 1786 * @since 4.2.0 1787 * 1788 * @param string $charset The character set to use. Default null. 1789 * @param string $table The name of the table being checked. 1790 */ 1791 $charset = apply_filters( 'pre_get_table_charset', null, $table ); 1792 if ( null !== $charset ) { 1793 return $charset; 1794 } 1795 1796 if ( isset( $this->table_charset[ $tablekey ] ) ) { 1797 return $this->table_charset[ $tablekey ]; 1798 } 1799 1800 $charsets = $columns = array(); 1801 $results = $this->get_results( "SHOW FULL COLUMNS FROM `$table`" ); 1802 if ( ! $results ) { 1803 return new WP_Error( 'wpdb_get_table_charset_failure' ); 1804 } 1805 1806 foreach ( $results as $column ) { 1807 $columns[ strtolower( $column->Field ) ] = $column; 1808 } 1809 1810 $this->col_meta[ $tablekey ] = $columns; 1811 1812 foreach ( $columns as $column ) { 1813 if ( ! empty( $column->Collation ) ) { 1814 list( $charset ) = explode( '_', $column->Collation ); 1815 $charsets[ strtolower( $charset ) ] = true; 1816 } 1817 1818 list( $type ) = explode( '(', $column->Type ); 1819 1820 // A binary/blob means the whole query gets treated like this. 1821 if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) { 1822 $this->table_charset[ $tablekey ] = 'binary'; 1823 return 'binary'; 1824 } 1825 } 1826 1827 // utf8mb3 is an alias for utf8. 1828 if ( isset( $charsets['utf8mb3'] ) ) { 1829 $charsets['utf8'] = true; 1830 unset( $charsets['utf8mb3'] ); 1831 } 1832 1833 // Check if we have more than one charset in play. 1834 $count = count( $charsets ); 1835 if ( 1 === $count ) { 1836 $charset = key( $charsets ); 1837 } elseif ( 0 === $count ) { 1838 // No charsets, assume this table can store whatever. 1839 $charset = false; 1840 } else { 1841 // More than one charset. Remove latin1 if present and recalculate. 1842 unset( $charsets['latin1'] ); 1843 $count = count( $charsets ); 1844 if ( 1 === $count ) { 1845 // Only one charset (besides latin1). 1846 $charset = key( $charsets ); 1847 } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) { 1848 // Two charsets, but they're utf8 and utf8mb4, use utf8. 1849 $charset = 'utf8'; 1850 } else { 1851 // Two mixed character sets. ascii. 1852 $charset = 'ascii'; 1853 } 1854 } 1855 1856 $this->table_charset[ $tablekey ] = $charset; 1857 return $charset; 1858 } 1859 1860 /** 1861 * Retrieves the character set for the given column. 1862 * 1863 * @since 4.2.0 1864 * @access public 1865 * 1866 * @param string $table Table name. 1867 * @param string $column Column name. 1868 * @return mixed Column character set as a string. False if the column has no 1869 * character set. {@see WP_Error} object if there was an error. 1870 */ 1871 public function get_col_charset( $table, $column ) { 1872 $tablekey = strtolower( $table ); 1873 $columnkey = strtolower( $column ); 1874 1875 /** 1876 * Filter the column charset value before the DB is checked. 1877 * 1878 * Passing a non-null value to the filter will short-circuit 1879 * checking the DB for the charset, returning that value instead. 1880 * 1881 * @since 4.2.0 1882 * 1883 * @param string $charset The character set to use. Default null. 1884 * @param string $table The name of the table being checked. 1885 * @param string $column The name of the column being checked. 1886 */ 1887 $charset = apply_filters( 'pre_get_col_charset', null, $table, $column ); 1888 if ( null !== $charset ) { 1889 return $charset; 1890 } 1891 1892 // Skip this entirely if this isn't a MySQL database. 1893 if ( false === $this->is_mysql ) { 1894 return false; 1895 } 1896 1897 if ( empty( $this->table_charset[ $tablekey ] ) ) { 1898 // This primes column information for us. 1899 $table_charset = $this->get_table_charset( $table ); 1900 if ( is_wp_error( $table_charset ) ) { 1901 return $table_charset; 1902 } 1903 } 1904 1905 // If still no column information, return the table charset. 1906 if ( empty( $this->col_meta[ $tablekey ] ) ) { 1907 return $this->table_charset[ $tablekey ]; 1908 } 1909 1910 // If this column doesn't exist, return the table charset. 1911 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) { 1912 return $this->table_charset[ $tablekey ]; 1913 } 1914 1915 // Return false when it's not a string column. 1916 if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) { 1917 return false; 1918 } 1919 1920 list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation ); 1921 return $charset; 1922 } 1923 1924 /** 1925 * Check if a string is ASCII. 1926 * 1927 * The negative regex is faster for non-ASCII strings, as it allows 1928 * the search to finish as soon as it encounters a non-ASCII character. 1929 * 1930 * @since 4.2.0 1931 * @access protected 1932 * 1933 * @param string $string String to check. 1934 * @return bool True if ASCII, false if not. 1935 */ 1936 protected function check_ascii( $string ) { 1937 if ( function_exists( 'mb_check_encoding' ) ) { 1938 if ( mb_check_encoding( $string, 'ASCII' ) ) { 1939 return true; 1940 } 1941 } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) { 1942 return true; 1943 } 1944 1945 return false; 1946 } 1947 1948 /** 1949 * Check if the query is accessing a collation considered safe on the current version of MySQL. 1950 * 1951 * @since 4.2.0 1952 * @access protected 1953 * 1954 * @param string $query The query to check. 1955 * @return bool True if the collation is safe, false if it isn't. 1956 */ 1957 protected function check_safe_collation( $query ) { 1958 if ( $this->checking_collation ) { 1959 return true; 1960 } 1961 $table = $this->get_table_from_query( $query ); 1962 if ( ! $table ) { 1963 return false; 1964 } 1965 1966 $this->checking_collation = true; 1967 $this->get_table_charset( $table ); 1968 $this->checking_collation = false; 1969 1970 $table = strtolower( $table ); 1971 if ( empty( $this->col_meta[ $table ] ) ) { 1972 return false; 1973 } 1974 1975 foreach( $this->col_meta[ $table ] as $col ) { 1976 if ( empty( $col->Collation ) ) { 1977 continue; 1978 } 1979 1980 if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) { 1981 return false; 1982 } 1983 } 1984 1985 return true; 1986 } 1987 1988 /** 1989 * Strips any invalid characters based on value/charset pairs. 1990 * 1991 * @since 4.2.0 1992 * @access protected 1993 * 1994 * @param array $data Array of value arrays. Each value array has the keys 1995 * 'value' and 'charset'. An optional 'ascii' key can be 1996 * set to false to avoid redundant ASCII checks. 1997 * @return array|WP_Error The $data parameter, with invalid characters removed from 1998 * each value. This works as a passthrough: any additional keys 1999 * such as 'field' are retained in each value array. If we cannot 2000 * remove invalid characters, a {@see WP_Error} object is returned. 2001 */ 2002 protected function strip_invalid_text( $data ) { 2003 // Some multibyte character sets that we can check in PHP. 2004 $mb_charsets = array( 2005 'ascii' => 'ASCII', 2006 'big5' => 'BIG-5', 2007 'eucjpms' => 'eucJP-win', 2008 'gb2312' => 'EUC-CN', 2009 'ujis' => 'EUC-JP', 2010 'utf32' => 'UTF-32', 2011 ); 2012 2013 $supported_charsets = array(); 2014 if ( function_exists( 'mb_list_encodings' ) ) { 2015 $supported_charsets = mb_list_encodings(); 2016 } 2017 2018 $db_check_string = false; 2019 2020 foreach ( $data as &$value ) { 2021 $charset = $value['charset']; 2022 2023 // Column isn't a string, or is latin1, which will will happily store anything. 2024 if ( false === $charset || 'latin1' === $charset ) { 2025 continue; 2026 } 2027 2028 if ( ! is_string( $value['value'] ) ) { 2029 continue; 2030 } 2031 2032 // ASCII is always OK. 2033 if ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) ) { 2034 continue; 2035 } 2036 2037 // Convert the text locally. 2038 if ( $supported_charsets ) { 2039 if ( isset( $mb_charsets[ $charset ] ) && in_array( $mb_charsets[ $charset ], $supported_charsets ) ) { 2040 $value['value'] = mb_convert_encoding( $value['value'], $mb_charsets[ $charset ], $mb_charsets[ $charset ] ); 2041 continue; 2042 } 2043 } 2044 2045 // utf8 can be handled by regex, which is a bunch faster than a DB lookup. 2046 if ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) { 2047 $regex = '/ 2048 ( 2049 (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx 2050 | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx 2051 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2 2052 | [\xE1-\xEC][\x80-\xBF]{2} 2053 | \xED[\x80-\x9F][\x80-\xBF] 2054 | [\xEE-\xEF][\x80-\xBF]{2}'; 2055 2056 if ( 'utf8mb4' === $charset) { 2057 $regex .= ' 2058 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3 2059 | [\xF1-\xF3][\x80-\xBF]{3} 2060 | \xF4[\x80-\x8F][\x80-\xBF]{2} 2061 '; 2062 } 2063 2064 $regex .= '){1,50} # ...one or more times 2065 ) 2066 | . # anything else 2067 /x'; 2068 $value['value'] = preg_replace( $regex, '$1', $value['value'] ); 2069 continue; 2070 } 2071 2072 // We couldn't use any local conversions, send it to the DB. 2073 $value['db'] = $db_check_string = true; 2074 } 2075 unset( $value ); // Remove by reference. 2076 2077 if ( $db_check_string ) { 2078 $queries = array(); 2079 foreach ( $data as $col => $value ) { 2080 if ( ! empty( $value['db'] ) ) { 2081 if ( ! isset( $queries[ $value['charset'] ] ) ) { 2082 $queries[ $value['charset'] ] = array(); 2083 } 2084 2085 // Split the CONVERT() calls by charset, so we can make sure the connection is right 2086 $queries[ $value['charset'] ][ $col ] = $this->prepare( "CONVERT( %s USING {$value['charset']} )", $value['value'] ); 2087 } 2088 } 2089 2090 $connection_charset = $this->charset; 2091 foreach ( $queries as $charset => $query ) { 2092 if ( ! $query ) { 2093 continue; 2094 } 2095 2096 // Change the charset to match the string(s) we're converting 2097 if ( $charset !== $connection_charset ) { 2098 $connection_charset = $charset; 2099 $this->set_charset( $this->dbh, $charset ); 2100 } 2101 2102 $this->check_current_query = false; 2103 2104 $row = $this->get_row( "SELECT " . implode( ', ', $query ), ARRAY_N ); 2105 if ( ! $row ) { 2106 $this->set_charset( $this->dbh, $connection_charset ); 2107 return new WP_Error( 'wpdb_strip_invalid_text_failure' ); 2108 } 2109 2110 $cols = array_keys( $query ); 2111 $col_count = count( $cols ); 2112 for ( $ii = 0; $ii < $col_count; $ii++ ) { 2113 $data[ $cols[ $ii ] ]['value'] = $row[ $ii ]; 2114 } 2115 } 2116 2117 // Don't forget to change the charset back! 2118 if ( $connection_charset !== $this->charset ) { 2119 $this->set_charset( $this->dbh ); 2120 } 2121 } 2122 2123 return $data; 2124 } 2125 2126 /** 2127 * Strips any invalid characters from the query. 2128 * 2129 * @since 4.2.0 2130 * @access protected 2131 * 2132 * @param string $query Query to convert. 2133 * @return string|WP_Error The converted query, or a {@see WP_Error} object if the conversion fails. 2134 */ 2135 protected function strip_invalid_text_from_query( $query ) { 2136 $table = $this->get_table_from_query( $query ); 2137 if ( $table ) { 2138 $charset = $this->get_table_charset( $table ); 2139 if ( is_wp_error( $charset ) ) { 2140 return $charset; 2141 } 2142 2143 // We can't reliably strip text from tables containing binary/blob columns 2144 if ( 'binary' === $charset ) { 2145 return $query; 2146 } 2147 } else { 2148 $charset = $this->charset; 2149 } 2150 2151 $data = array( 2152 'value' => $query, 2153 'charset' => $charset, 2154 'ascii' => false, 2155 ); 2156 2157 $data = $this->strip_invalid_text( array( $data ) ); 2158 if ( is_wp_error( $data ) ) { 2159 return $data; 2160 } 2161 2162 return $data[0]['value']; 2163 } 2164 2165 /** 2166 * Strips any invalid characters from the string for a given table and column. 2167 * 2168 * @since 4.2.0 2169 * @access public 2170 * 2171 * @param string $table Table name. 2172 * @param string $column Column name. 2173 * @param string $value The text to check. 2174 * @return string|WP_Error The converted string, or a `WP_Error` object if the conversion fails. 2175 */ 2176 public function strip_invalid_text_for_column( $table, $column, $value ) { 2177 if ( ! is_string( $value ) || $this->check_ascii( $value ) ) { 2178 return $value; 2179 } 2180 2181 $charset = $this->get_col_charset( $table, $column ); 2182 if ( ! $charset ) { 2183 // Not a string column. 2184 return $value; 2185 } elseif ( is_wp_error( $charset ) ) { 2186 // Bail on real errors. 2187 return $charset; 2188 } 2189 2190 $data = array( 2191 $column => array( 2192 'value' => $value, 2193 'charset' => $charset, 2194 'ascii' => false, 2195 ) 2196 ); 2197 2198 $data = $this->strip_invalid_text( $data ); 2199 if ( is_wp_error( $data ) ) { 2200 return $data; 2201 } 2202 2203 return $data[ $column ]['value']; 2204 } 2205 2206 /** 2207 * Find the first table name referenced in a query. 2208 * 2209 * @since 4.2.0 2210 * @access protected 2211 * 2212 * @param string $query The query to search. 2213 * @return string|false $table The table name found, or false if a table couldn't be found. 2214 */ 2215 protected function get_table_from_query( $query ) { 2216 // Remove characters that can legally trail the table name. 2217 $query = rtrim( $query, ';/-#' ); 2218 2219 // Allow (select...) union [...] style queries. Use the first query's table name. 2220 $query = ltrim( $query, "\r\n\t (" ); 2221 2222 /* 2223 * Strip everything between parentheses except nested selects and use only 1,000 2224 * chars of the query. 2225 */ 2226 $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', substr( $query, 0, 1000 ) ); 2227 2228 // Quickly match most common queries. 2229 if ( preg_match( '/^\s*(?:' 2230 . 'SELECT.*?\s+FROM' 2231 . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?' 2232 . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?' 2233 . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?' 2234 . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?' 2235 . ')\s+`?([\w-]+)`?/is', $query, $maybe ) ) { 2236 return $maybe[1]; 2237 } 2238 2239 // SHOW TABLE STATUS and SHOW TABLES 2240 if ( preg_match( '/^\s*(?:' 2241 . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' 2242 . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)' 2243 . ')\W([\w-]+)\W/is', $query, $maybe ) ) { 2244 return $maybe[1]; 2245 } 2246 2247 // Big pattern for the rest of the table-related queries. 2248 if ( preg_match( '/^\s*(?:' 2249 . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM' 2250 . '|DESCRIBE|DESC|EXPLAIN|HANDLER' 2251 . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?' 2252 . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE' 2253 . '|TRUNCATE(?:\s+TABLE)?' 2254 . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?' 2255 . '|ALTER(?:\s+IGNORE)?\s+TABLE' 2256 . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?' 2257 . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON' 2258 . '|DROP\s+INDEX.*\s+ON' 2259 . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE' 2260 . '|(?:GRANT|REVOKE).*ON\s+TABLE' 2261 . '|SHOW\s+(?:.*FROM|.*TABLE)' 2262 . ')\s+\(*\s*`?([\w-]+)`?\s*\)*/is', $query, $maybe ) ) { 2263 return $maybe[1]; 2264 } 2265 2266 return false; 2267 } 2268 1574 2269 /** 1575 2270 * Load the column metadata from the last query.
Note: See TracChangeset
for help on using the changeset viewer.