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