Ticket #29642: 29642.unified.patch
File 29642.unified.patch, 32.4 KB (added by , 11 years ago) |
---|
-
new file src/wp-includes/class-wp-recursive-query.php
diff --git src/wp-includes/class-wp-recursive-query.php src/wp-includes/class-wp-recursive-query.php new file mode 100644 index 0000000..5fec3f5
- + 1 <?php 2 3 /** 4 * Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params. 5 * 6 * @since 4.1.0 7 */ 8 abstract class WP_Recursive_Query { 9 10 /** 11 * Query arguments passed to the constructor. 12 * 13 * @since 4.1.0 14 * @access public 15 * @var array 16 */ 17 public $queries = array(); 18 19 /** 20 * A flat list of table aliases used in JOIN clauses. 21 * 22 * @since 4.1.0 23 * @access protected 24 * @var array 25 */ 26 protected $table_aliases = array(); 27 28 /** 29 * Generate SQL clauses to be appended to a main query. 30 * 31 * Extending classes should call this method from within a publicly 32 * accessible get_sql() method, and manipulate the SQL as necessary. 33 * For example, {@link WP_Tax_Query::get_sql()} is merely a wrapper for 34 * get_sql_clauses(), while {@link WP_Date_Query::get_sql()} discards 35 * the empty 'join' clauses are discarded, and passes the 'where' 36 * clause through apply_filters(). 37 * 38 * @since 4.1.0 39 * @access protected 40 * 41 * @param string $primary_table 42 * @param string $primary_id_column 43 * @return array 44 */ 45 protected function get_sql_clauses() { 46 $sql = $this->get_sql_for_query( $this->queries ); 47 48 if ( ! empty( $sql['where'] ) ) { 49 $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n"; 50 } 51 52 return $sql; 53 } 54 55 /** 56 * Generate SQL clauses for a single query array. 57 * 58 * If nested subqueries are found, this method recurses the tree to 59 * produce the properly nested SQL. 60 * 61 * Subclasses generally do not need to call this method. It is invoked 62 * automatically from get_sql_clauses(). 63 * 64 * @since 4.1.0 65 * @access protected 66 * 67 * @param array $query Query to parse. 68 * @param int $depth Optional. Number of tree levels deep we 69 * currently are. Used to calculate indentation. 70 * @return array 71 */ 72 protected function get_sql_for_query( $query, $depth = 0 ) { 73 $sql_chunks = array( 74 'join' => array(), 75 'where' => array(), 76 ); 77 78 $sql = array( 79 'join' => '', 80 'where' => '', 81 ); 82 83 $indent = ''; 84 for ( $i = 0; $i < $depth; $i++ ) { 85 $indent .= "\t"; 86 } 87 88 foreach ( $query as $key => $clause ) { 89 if ( 'relation' === $key ) { 90 $relation = $query['relation']; 91 } else if ( is_array( $clause ) ) { 92 // This is a first-order clause 93 if ( $this->is_first_order_clause( $clause ) ) { 94 $clause_sql = $this->get_sql_for_clause( $clause, $query ); 95 96 $where_count = count( $clause_sql['where'] ); 97 if ( ! $where_count ) { 98 $sql_chunks['where'][] = ''; 99 } else if ( 1 === $where_count ) { 100 $sql_chunks['where'][] = $clause_sql['where'][0]; 101 } else { 102 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 103 } 104 105 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 106 // This is a subquery 107 } else { 108 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 109 110 $sql_chunks['where'][] = $clause_sql['where']; 111 $sql_chunks['join'][] = $clause_sql['join']; 112 113 } 114 } 115 } 116 117 // Filter empties 118 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 119 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 120 121 if ( empty( $relation ) ) { 122 $relation = 'AND'; 123 } 124 125 if ( ! empty( $sql_chunks['join'] ) ) { 126 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 127 } 128 129 if ( ! empty( $sql_chunks['where'] ) ) { 130 $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n"; 131 } 132 133 return $sql; 134 } 135 136 /** 137 * Generate JOIN and WHERE clauses for a first-order clause. 138 * 139 * Must be overridden in a subclass. 140 * 141 * @since 4.1.0 142 * @access protected 143 * 144 * @param array $clause Array of arguments belonging to the clause. 145 * @param array $parent_query Parent query to which the clause belongs. 146 * @return array { 147 * @type array $join Array of subclauses for the JOIN statement. 148 * @type array $where Array of subclauses for the WHERE statement. 149 * } 150 */ 151 abstract protected function get_sql_for_clause( $clause, $parent_query ); 152 153 /** 154 * Determine whether a clause is first-order. 155 * 156 * Must be overridden in a subclass. 157 * 158 * @since 4.1.0 159 * @access protected 160 * 161 * @param array $q Clause to check. 162 * @return bool 163 */ 164 abstract protected function is_first_order_clause( $query ); 165 } -
src/wp-includes/date.php
diff --git src/wp-includes/date.php src/wp-includes/date.php index 1cf4c6d..dbad09e 100644
8 8 * 9 9 * @since 3.7.0 10 10 */ 11 class WP_Date_Query { 12 /** 13 * List of date queries. 14 * 15 * @since 3.7.0 16 * @access public 17 * @var array 18 */ 19 public $queries = array(); 11 class WP_Date_Query extends WP_Recursive_Query { 20 12 21 13 /** 22 * The relation between the queries. Can be either 'AND' or 'OR' and can be changed via the query arguments.14 * The column to query against. Can be changed via the query arguments. 23 15 * 24 16 * @since 3.7.0 25 17 * @access public 26 18 * @var string 27 19 */ 28 public $ relation = 'AND';20 public $column = 'post_date'; 29 21 30 22 /** 31 * The column to query against. Can be changed via the query arguments.23 * The value comparison operator. Can be changed via the query arguments. 32 24 * 33 25 * @since 3.7.0 34 26 * @access public 35 * @var string27 * @var array 36 28 */ 37 public $co lumn = 'post_date';29 public $compare = '='; 38 30 39 31 /** 40 * The value comparison operator. Can be changed via the query arguments.32 * Supported time-related parameter keys. 41 33 * 42 * @since 3.7.034 * @since 4.1.0 43 35 * @access public 44 36 * @var array 45 37 */ 46 public $compare = '='; 38 public $time_keys = array( 'after', 'before', 'year', 'month', 'monthnum', 'week', 'w', 'dayofyear', 'day', 'dayofweek', 'hour', 'minute', 'second' ); 39 47 40 48 41 /** 49 42 * Constructor. … … class WP_Date_Query { 109 102 * 'comment_date', 'comment_date_gmt'. 110 103 */ 111 104 public function __construct( $date_query, $default_column = 'post_date' ) { 112 if ( empty( $date_query ) || ! is_array( $date_query ) )113 return;114 105 115 if ( isset( $date_query['relation'] ) && strtoupper( $date_query['relation'] ) == 'OR' )106 if ( isset( $date_query['relation'] ) && 'OR' === strtoupper( $date_query['relation'] ) ) { 116 107 $this->relation = 'OR'; 117 else108 } else { 118 109 $this->relation = 'AND'; 110 } 111 112 if ( ! is_array( $date_query ) ) { 113 return; 114 } 119 115 120 if ( ! empty( $date_query['column'] ) ) 121 $this->column = esc_sql( $date_query['column'] ); 122 else 123 $this->column = esc_sql( $default_column ); 116 if ( ! isset( $date_query[0] ) && ! empty( $date_query ) ) { 117 $date_query = array( $date_query ); 118 } 119 120 if ( empty( $date_query ) ) { 121 return; 122 } 123 124 if ( ! empty( $date_query['column'] ) ) { 125 $date_query['column'] = esc_sql( $date_query['column'] ); 126 } else { 127 $date_query['column'] = esc_sql( $default_column ); 128 } 124 129 125 130 $this->column = $this->validate_column( $this->column ); 126 131 127 132 $this->compare = $this->get_compare( $date_query ); 128 133 129 // If an array of arrays wasn't passed, fix it 130 if ( ! isset( $date_query[0] ) ) 131 $date_query = array( $date_query ); 134 $this->queries = $this->sanitize_query( $date_query ); 135 136 return; 137 } 138 139 /** 140 * Recursive-friendly query sanitizer. 141 * 142 * Ensures that each query-level clause has a 'relation' key, and that 143 * each first-order clause contains all the necessary keys from 144 * $defaults. 145 * 146 * @since 4.1.0 147 * 148 * @param array $query A tax_query query clause. 149 * @return array 150 */ 151 protected function sanitize_query( $query, $parent_query = null ) { 152 $sanitized_query = array(); 153 154 $defaults = array( 155 'column' => 'post_date', 156 'compare' => '=', 157 'relation' => 'AND', 158 ); 132 159 133 $this->queries = array(); 134 foreach ( $date_query as $key => $query ) { 135 if ( ! is_array( $query ) ) 160 // Numeric keys should always have array values. 161 foreach ( $query as $qkey => $qvalue ) { 162 if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) { 163 unset( $query[ $qkey ] ); 164 } 165 } 166 167 // Each layer of query should have a value for each default key. 168 foreach ( $defaults as $dkey => $dvalue ) { 169 if ( isset( $query[ $dkey ] ) ) { 136 170 continue; 171 } 172 173 if ( isset( $parent_query[ $dkey ] ) ) { 174 $query[ $dkey ] = $parent_query[ $dkey ]; 175 } else { 176 $query[ $dkey ] = $dvalue; 177 } 178 } 137 179 138 $this->queries[$key] = $query; 180 foreach ( $query as $key => $q ) { 181 if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { 182 $sanitized_query[ $key ] = $q; 183 } else { 184 $sanitized_query[] = $this->sanitize_query( $q, $query ); 185 } 139 186 } 187 188 return $sanitized_query; 140 189 } 141 190 142 191 /** … … class WP_Date_Query { 192 241 * @return string MySQL WHERE parameters 193 242 */ 194 243 public function get_sql() { 195 // The parts of the final query 196 $where = array(); 197 198 foreach ( $this->queries as $key => $query ) { 199 $where_parts = $this->get_sql_for_subquery( $query ); 200 if ( $where_parts ) { 201 // Combine the parts of this subquery into a single string 202 $where[ $key ] = '( ' . implode( ' AND ', $where_parts ) . ' )'; 203 } 204 } 244 $sql = $this->get_sql_clauses(); 205 245 206 // Combine the subquery strings into a single string 207 if ( $where ) 208 $where = ' AND ( ' . implode( " {$this->relation} ", $where ) . ' )'; 209 else 210 $where = ''; 246 $where = $sql['where']; 211 247 212 248 /** 213 249 * Filter the date query WHERE clause. … … class WP_Date_Query { 221 257 } 222 258 223 259 /** 224 * Turns a single date subqueryinto pieces for a WHERE clause.260 * Turns a single date clause into pieces for a WHERE clause. 225 261 * 226 262 * @since 3.7.0 227 263 * return array 228 264 */ 229 protected function get_sql_for_ subquery( $query ) {265 protected function get_sql_for_clause( $query, $parent_query ) { 230 266 global $wpdb; 231 267 232 268 // The sub-parts of a $where part … … class WP_Date_Query { 293 329 } 294 330 } 295 331 296 return $where_parts; 332 // Return an array of 'join' and 'where' for compatibility 333 // with other query classes. 334 return array( 335 'where' => $where_parts, 336 'join' => array(), 337 ); 338 } 339 340 protected function is_first_order_clause( $query ) { 341 return isset( $query['after'] ) || isset( $query['before'] ) || isset( $query['year'] ) || isset( $query['month'] ) || isset( $query['monthnum'] ) || isset( $query['week'] ) || isset( $query['w'] ) || isset( $query['dayofyear'] ) || isset( $query['day'] ) || isset( $query['dayofweek'] ) || isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ); 297 342 } 298 343 299 344 /** -
src/wp-includes/meta.php
diff --git src/wp-includes/meta.php src/wp-includes/meta.php index e3c43cf..3b452fc 100644
function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $ 853 853 * 854 854 * @since 3.2.0 855 855 */ 856 class WP_Meta_Query {856 class WP_Meta_Query extends WP_Recursive_Query { 857 857 /** 858 858 * List of metadata queries. A single query is an associative array: 859 859 * - 'key' string The meta key … … class WP_Meta_Query { 882 882 public $relation; 883 883 884 884 /** 885 * Constructor885 * Database table to query for the metadata. 886 886 * 887 * @param array $meta_query (optional) A meta query 887 * @access public 888 * @var string 889 */ 890 public $meta_table; 891 892 /** 893 * Column in meta_table that represents the ID of the object the metadata belongs to. 894 * 895 * @access public 896 * @var string 897 */ 898 public $meta_id_column; 899 900 /** 901 * Database table that where the metadata's objects are stored (eg $wpdb->users). 902 * 903 * @access public 904 * @var string 905 */ 906 public $primary_table; 907 908 /** 909 * Column in primary_table that represents the ID of the object. 910 * 911 * @access public 912 * @var string 913 */ 914 public $primary_id_column; 915 916 /** 917 * Constructor. 918 * 919 * @param array $meta_query (optional) A meta query. 888 920 */ 889 921 public function __construct( $meta_query = false ) { 890 922 if ( !$meta_query ) … … class WP_Meta_Query { 896 928 $this->relation = 'AND'; 897 929 } 898 930 899 $this->queries = array(); 931 $this->queries = $this->sanitize_query( $meta_query ); 932 } 933 934 /** 935 * Ensure that the meta_query argument passed to the class constructor is well-formed. 936 * 937 * Eliminates empty items and ensures that a 'relation' is set. 938 * 939 * @param array $queries 940 * @return array 941 */ 942 public function sanitize_query( $queries ) { 943 $clean_queries = array(); 944 945 if ( ! is_array( $queries ) ) { 946 return $clean_queries; 947 } 948 949 foreach ( $queries as $key => $query ) { 950 if ( 'relation' === $key ) { 951 $relation = $query; 952 953 // First-order clause. 954 } else if ( $this->is_first_order_clause( $query ) ) { 955 $clean_queries[] = $query; 956 957 // Otherwise, it's a nested query. 958 } else { 959 $cleaned_query = $this->sanitize_query( $query ); 960 961 if ( ! empty( $cleaned_query ) ) { 962 $clean_queries[] = $cleaned_query; 963 } 964 } 965 } 966 967 if ( empty( $clean_queries ) ) { 968 return $clean_queries; 969 } 970 971 // Sanitize the 'relation' key provided in the query. 972 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 973 $clean_queries['relation'] = 'OR'; 900 974 901 foreach ( $meta_query as $key => $query ) { 902 if ( ! is_array( $query ) ) 903 continue; 975 // If there is only a single clause, call the relation 'OR'. 976 // This value will not actually be used to join clauses, but it 977 // simplifies the logic around combining key-only queries. 978 } else if ( 1 === count( $clean_queries ) ) { 979 $clean_queries['relation'] = 'OR'; 904 980 905 $this->queries[] = $query; 981 // Default to AND. 982 } else { 983 $clean_queries['relation'] = 'AND'; 906 984 } 985 986 return $clean_queries; 987 } 988 989 protected function is_first_order_clause( $query ) { 990 return isset( $query['key'] ) || isset( $query['value'] ); 907 991 } 908 992 909 993 /** … … class WP_Meta_Query { 958 1042 } 959 1043 960 1044 /** 961 * Generate s SQL clauses to be appended to a main query.1045 * Generate SQL JOIN and WHERE clauses for a first-order query clause. 962 1046 * 963 * @since 3.2.0 964 * @access public 1047 * "First-order" means that it's an array with a 'key' or 'value'. 965 1048 * 966 * @param string $type Type of meta 967 * @param string $primary_table 968 * @param string $primary_id_column 969 * @param object $context (optional) The main query object 970 * @return array( 'join' => $join_sql, 'where' => $where_sql ) 1049 * @param array $clause Query clause. 1050 * @param array $parent_query Parent query array. 1051 * @return array 971 1052 */ 972 public function get_sql ( $type, $primary_table, $primary_id_column, $context = null) {1053 public function get_sql_for_clause( $clause, $parent_query ) { 973 1054 global $wpdb; 974 1055 975 if ( ! $meta_table = _get_meta_table( $type ) ) 976 return false; 977 978 $meta_id_column = sanitize_key( $type . '_id' ); 979 980 $join = array(); 981 $where = array(); 1056 $sql_chunks = array( 1057 'where' => array(), 1058 'join' => array(), 1059 ); 982 1060 983 $ key_only_queries = array();984 $ queries = array();1061 $i = count( $this->table_aliases ); 1062 $alias = $i ? 'mt' . $i : $this->meta_table; 985 1063 986 // Split out the queries with empty arrays as value 987 foreach ( $this->queries as $k => $q ) { 988 if ( isset( $q['value'] ) && is_array( $q['value'] ) && empty( $q['value'] ) ) { 989 $key_only_queries[$k] = $q; 990 unset( $this->queries[$k] ); 991 } 992 } 993 994 // Split out the meta_key only queries (we can only do this for OR) 995 if ( 'OR' == $this->relation ) { 996 foreach ( $this->queries as $k => $q ) { 997 if ( ( empty( $q['compare'] ) || 'NOT EXISTS' != $q['compare'] ) && ! array_key_exists( 'value', $q ) && ! empty( $q['key'] ) ) 998 $key_only_queries[$k] = $q; 999 else 1000 $queries[$k] = $q; 1001 } 1064 if ( isset( $clause['compare'] ) ) { 1065 $meta_compare = strtoupper( $clause['compare'] ); 1002 1066 } else { 1003 $ queries = $this->queries;1067 $meta_compare = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 1004 1068 } 1005 1069 1006 // Specify all the meta_key only queries in one go 1007 if ( $key_only_queries ) { 1008 $join[] = "INNER JOIN $meta_table ON $primary_table.$primary_id_column = $meta_table.$meta_id_column"; 1009 1010 foreach ( $key_only_queries as $key => $q ) 1011 $where["key-only-$key"] = $wpdb->prepare( "$meta_table.meta_key = %s", trim( $q['key'] ) ); 1070 if ( ! in_array( $meta_compare, array( 1071 '=', '!=', '>', '>=', '<', '<=', 1072 'LIKE', 'NOT LIKE', 1073 'IN', 'NOT IN', 1074 'BETWEEN', 'NOT BETWEEN', 1075 'EXISTS', 'NOT EXISTS', 1076 'REGEXP', 'NOT REGEXP', 'RLIKE' 1077 ) ) ) { 1078 $meta_compare = '='; 1012 1079 } 1013 1080 1014 foreach ( $queries as $k => $q ) { 1015 $meta_key = isset( $q['key'] ) ? trim( $q['key'] ) : ''; 1016 $meta_type = $this->get_cast_for_type( isset( $q['type'] ) ? $q['type'] : '' ); 1017 1018 if ( array_key_exists( 'value', $q ) && is_null( $q['value'] ) ) 1019 $q['value'] = ''; 1020 1021 $meta_value = isset( $q['value'] ) ? $q['value'] : null; 1022 1023 if ( isset( $q['compare'] ) ) 1024 $meta_compare = strtoupper( $q['compare'] ); 1025 else 1026 $meta_compare = is_array( $meta_value ) ? 'IN' : '='; 1027 1028 if ( ! in_array( $meta_compare, array( 1029 '=', '!=', '>', '>=', '<', '<=', 1030 'LIKE', 'NOT LIKE', 1031 'IN', 'NOT IN', 1032 'BETWEEN', 'NOT BETWEEN', 1033 'NOT EXISTS', 1034 'REGEXP', 'NOT REGEXP', 'RLIKE' 1035 ) ) ) 1036 $meta_compare = '='; 1037 1038 $i = count( $join ); 1039 $alias = $i ? 'mt' . $i : $meta_table; 1040 1041 if ( 'NOT EXISTS' == $meta_compare ) { 1042 $join[$i] = "LEFT JOIN $meta_table"; 1043 $join[$i] .= $i ? " AS $alias" : ''; 1044 $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column AND $alias.meta_key = '$meta_key')"; 1045 1046 $where[$k] = ' ' . $alias . '.' . $meta_id_column . ' IS NULL'; 1047 1048 continue; 1081 // There are a number of different query structures that get 1082 // built in different ways. 1083 // 1. Key-only clauses - (a) clauses without a 'value' key that 1084 // appear in the context of an OR relation and do not use 1085 // 'NOT EXISTS' as the 'compare', or (b) clauses with an 1086 // empty array for 'value'. 1087 if ( ! empty( $clause['key'] ) && ( 1088 ( ! array_key_exists( 'value', $clause ) && 'NOT EXISTS' !== $meta_compare && 'OR' === $parent_query['relation'] ) || 1089 ( isset( $clause['value'] ) && is_array( $clause['value'] ) && empty( $clause['value'] ) ) 1090 ) ) { 1091 1092 $alias = $this->meta_table; 1093 $sql_chunks['join'][] = " INNER JOIN $this->meta_table ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)"; 1094 $sql_chunks['where'][] = $wpdb->prepare( "$this->meta_table.meta_key = %s", trim( $clause['key'] ) ); 1095 1096 // 2. NOT EXISTS. 1097 } else if ( 'NOT EXISTS' === $meta_compare ) { 1098 $join = " LEFT JOIN $this->meta_table"; 1099 $join .= $i ? " AS $alias" : ''; 1100 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); 1101 $sql_chunks['join'][] = $join; 1102 1103 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; 1104 1105 // 3. EXISTS and other key-only queries. 1106 } else if ( 'EXISTS' === $meta_compare || ( ! empty( $clause['key'] ) && ! array_key_exists( 'value', $clause ) ) ) { 1107 $join = " INNER JOIN $this->meta_table"; 1108 $join .= $i ? " AS $alias" : ''; 1109 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; 1110 $sql_chunks['join'][] = $join; 1111 1112 $sql_chunks['where'][] = $wpdb->prepare( $alias . '.meta_key = %s', trim( $clause['key'] ) ); 1113 1114 // 4. Clauses that have a value. 1115 } else if ( array_key_exists( 'value', $clause ) ) { 1116 $join = " INNER JOIN $this->meta_table"; 1117 $join .= $i ? " AS $alias" : ''; 1118 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column)"; 1119 $sql_chunks['join'][] = $join; 1120 1121 if ( ! empty( $clause['key'] ) ) { 1122 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); 1049 1123 } 1050 1124 1051 $join[$i] = "INNER JOIN $meta_table"; 1052 $join[$i] .= $i ? " AS $alias" : ''; 1053 $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column)"; 1054 1055 $where[$k] = ''; 1056 if ( !empty( $meta_key ) ) 1057 $where[$k] = $wpdb->prepare( "$alias.meta_key = %s", $meta_key ); 1125 $meta_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' ); 1058 1126 1059 if ( is_null( $meta_value ) ) { 1060 if ( empty( $where[$k] ) ) 1061 unset( $join[$i] ); 1062 continue; 1063 } 1127 $meta_value = isset( $clause['value'] ) ? $clause['value'] : ''; 1064 1128 1065 1129 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 1066 if ( ! is_array( $meta_value ) ) 1130 if ( ! is_array( $meta_value ) ) { 1067 1131 $meta_value = preg_split( '/[,\s]+/', $meta_value ); 1068 1069 if ( empty( $meta_value ) ) {1070 unset( $join[$i] );1071 continue;1072 1132 } 1073 1133 } else { 1074 1134 $meta_value = trim( $meta_value ); 1075 1135 } 1076 1136 1077 if ( 'IN' == substr( $meta_compare, -2 ) ) {1137 if ( 'IN' == substr( $meta_compare, -2 ) ) { 1078 1138 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; 1079 } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) {1139 } elseif ( 'BETWEEN' == substr( $meta_compare, -7 ) ) { 1080 1140 $meta_value = array_slice( $meta_value, 0, 2 ); 1081 1141 $meta_compare_string = '%s AND %s'; 1082 1142 } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) { … … class WP_Meta_Query { 1086 1146 $meta_compare_string = '%s'; 1087 1147 } 1088 1148 1089 if ( ! empty( $where[$k] ) )1090 $where[$k] .= ' AND ';1149 $sql_chunks['where'][] = $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string}", $meta_value ); 1150 } 1091 1151 1092 $where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value ); 1152 // Multiple WHERE clauses (for meta_key and meta_value) should 1153 // be joined in parentheses. 1154 if ( 1 < count( $sql_chunks['where'] ) ) { 1155 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 1093 1156 } 1094 1157 1095 $ where = array_filter( $where );1158 $this->table_aliases[] = $alias; 1096 1159 1097 if ( empty( $where ) ) 1098 $where = ''; 1099 else 1100 $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )'; 1160 return $sql_chunks; 1161 } 1162 1163 /** 1164 * Generates SQL clauses to be appended to a main query. 1165 * 1166 * @since 3.2.0 1167 * @access public 1168 * 1169 * @param string $type Type of meta, eg 'user', 'post'. 1170 * @param string $primary_table Database table where the object being 1171 * filtered is stored (eg wp_users). 1172 * @param string $primary_id_column ID column for the filtered object in $primary_table 1173 * @param object $context Optional. The main query object. 1174 * @return array { 1175 * @type string $join SQL fragment to append to the main JOIN clause. 1176 * @type string $where SQL fragment to append to the main WHERE clause. 1177 * } 1178 */ 1179 public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { 1180 global $wpdb; 1181 1182 if ( ! $meta_table = _get_meta_table( $type ) ) { 1183 return false; 1184 } 1185 1186 $this->meta_table = $meta_table; 1187 $this->meta_id_column = sanitize_key( $type . '_id' ); 1188 1189 $this->primary_table = $primary_table; 1190 $this->primary_id_column = $primary_id_column; 1101 1191 1102 $join = implode( "\n", $join ); 1103 if ( ! empty( $join ) ) 1104 $join = ' ' . $join; 1192 $sql = $this->get_sql_clauses(); 1105 1193 1106 1194 /** 1107 1195 * Filter the meta query's generated SQL. … … class WP_Meta_Query { 1119 1207 * @type object $context The main query object. 1120 1208 * } 1121 1209 */ 1122 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) );1210 return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 1123 1211 } 1124 1212 } 1125 1213 -
src/wp-includes/taxonomy.php
diff --git src/wp-includes/taxonomy.php src/wp-includes/taxonomy.php index 1b4902d..52224cb 100644
function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) { 630 630 * 631 631 * @since 3.1.0 632 632 */ 633 class WP_Tax_Query {633 class WP_Tax_Query extends WP_Recursive_Query { 634 634 635 635 /** 636 636 * List of taxonomy queries. A single taxonomy query is an associative array: … … class WP_Tax_Query { 667 667 * @access private 668 668 * @var string 669 669 */ 670 private static $no_results = array( 'join' => '', 'where' => ' AND 0 = 1' ); 670 private static $no_results = array( 'join' => array( '' ), 'where' => array( '0 = 1' ) ); 671 672 /** 673 * Terms and taxonomies fetched by this query. 674 * 675 * We store this data in a flat array because they are referenced in a 676 * number of places by WP_Query. 677 * 678 * @since 4.1.0 679 * @access public 680 * @var array 681 */ 682 public $queried_terms = array(); 671 683 672 684 /** 673 685 * Constructor. … … class WP_Tax_Query { 699 711 $this->relation = 'AND'; 700 712 } 701 713 702 $defaults = array( 703 'taxonomy' => '', 704 'terms' => array(), 705 'include_children' => true, 706 'field' => 'term_id', 707 'operator' => 'IN', 708 ); 709 710 foreach ( $tax_query as $query ) { 711 if ( ! is_array( $query ) ) 712 continue; 713 714 $query = array_merge( $defaults, $query ); 715 716 $query['terms'] = (array) $query['terms']; 714 $this->queries = $this->sanitize_query( $tax_query ); 715 } 717 716 718 $this->queries[] = $query; 719 } 717 public function get_sql( $primary_table, $primary_id_column ) { 718 $this->primary_table = $primary_table; 719 $this->primary_id_column = $primary_id_column; 720 return $this->get_sql_clauses(); 720 721 } 721 722 722 723 /** 723 * Generate s SQL clauses to be appended to a main query.724 * Generate SQL JOIN and WHERE clauses for a first-order query clause. 724 725 * 725 * @since 3.1.0 726 * @access public 727 * 728 * @param string $primary_table 729 * @param string $primary_id_column 726 * @param array $clause Query clause. 727 * @param WP_Meta_Query $parent_query Query object. 730 728 * @return array 731 729 */ 732 p ublic function get_sql( $primary_table, $primary_id_column) {730 protected function get_sql_for_clause( $clause, $parent_query ) { 733 731 global $wpdb; 734 732 733 $sql = array( 734 'where' => array(), 735 'join' => array(), 736 ); 737 735 738 $join = ''; 736 $where = array();737 $i = 0;738 $count = count( $this->queries );739 739 740 foreach ( $this->queries as $index => $query ) { 741 $this->clean_query( $query ); 740 $this->clean_query( $clause ); 741 742 if ( is_wp_error( $clause ) ) { 743 return self::$no_results; 744 } 745 746 $terms = $clause['terms']; 747 $operator = strtoupper( $clause['operator'] ); 742 748 743 if ( is_wp_error( $query ) ) { 749 if ( 'IN' == $operator ) { 750 751 if ( empty( $terms ) ) { 744 752 return self::$no_results; 745 753 } 746 754 747 $terms = $query['terms']; 748 $operator = strtoupper( $query['operator'] ); 755 $terms = implode( ',', $terms ); 749 756 750 if ( 'IN' == $operator ) { 757 $i = count( $this->table_aliases ); 758 $alias = $i ? 'tt' . $i : $wpdb->term_relationships; 759 $this->table_aliases[] = $alias; 751 760 752 if ( empty( $terms ) ) { 753 if ( 'OR' == $this->relation ) { 754 if ( ( $index + 1 === $count ) && empty( $where ) ) { 755 return self::$no_results; 756 } 757 continue; 758 } else { 759 return self::$no_results; 760 } 761 } 761 $join .= " INNER JOIN $wpdb->term_relationships"; 762 $join .= $i ? " AS $alias" : ''; 763 $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)"; 762 764 763 $terms = implode( ',', $terms );765 $where = "$alias.term_taxonomy_id $operator ($terms)"; 764 766 765 $alias = $i ? 'tt' . $i : $wpdb->term_relationships;767 } elseif ( 'NOT IN' == $operator ) { 766 768 767 $join .= " INNER JOIN $wpdb->term_relationships";768 $join .= $i ? " AS $alias" : '';769 $join .= " ON ($primary_table.$primary_id_column = $alias.object_id)";769 if ( empty( $terms ) ) { 770 continue; 771 } 770 772 771 $where[] = "$alias.term_taxonomy_id $operator ($terms)"; 772 } elseif ( 'NOT IN' == $operator ) { 773 $terms = implode( ',', $terms ); 773 774 774 if ( empty( $terms ) ) { 775 continue; 776 } 775 $where = "$this->primary_table.$this->primary_id_column NOT IN ( 776 SELECT object_id 777 FROM $wpdb->term_relationships 778 WHERE term_taxonomy_id IN ($terms) 779 )"; 777 780 778 $terms = implode( ',', $terms );781 } elseif ( 'AND' == $operator ) { 779 782 780 $where[] = "$primary_table.$primary_id_column NOT IN ( 781 SELECT object_id 782 FROM $wpdb->term_relationships 783 WHERE term_taxonomy_id IN ($terms) 784 )"; 785 } elseif ( 'AND' == $operator ) { 783 if ( empty( $terms ) ) { 784 continue; 785 } 786 786 787 if ( empty( $terms ) ) { 788 continue; 789 } 787 $num_terms = count( $terms ); 790 788 791 $num_terms = count($terms );789 $terms = implode( ',', $terms ); 792 790 793 $terms = implode( ',', $terms ); 791 $where = "( 792 SELECT COUNT(1) 793 FROM $wpdb->term_relationships 794 WHERE term_taxonomy_id IN ($terms) 795 AND object_id = $this->primary_table.$this->primary_id_column 796 ) = $num_terms"; 797 } 794 798 795 $where[] = "( 796 SELECT COUNT(1) 797 FROM $wpdb->term_relationships 798 WHERE term_taxonomy_id IN ($terms) 799 AND object_id = $primary_table.$primary_id_column 800 ) = $num_terms"; 801 } 799 $sql['join'][] = $join; 800 $sql['where'][] = $where; 801 return $sql; 802 } 802 803 803 $i++; 804 } 804 /** 805 * Determine whether a clause is first-order. 806 * 807 * A "first-order" clause is one that contains any of the first-order 808 * clause keys ('terms', 'taxonomy', 'include_children', 'field', 809 * 'operator'). An empty clause also counts as a first-order clause, 810 * for backward compatibility. Any clause that doesn't meet this is 811 * determined, by process of elimination, to be a higher-order query. 812 * 813 * @since 4.1.0 814 * 815 * @param array $q Clause to check. 816 * @return bool 817 */ 818 protected function is_first_order_clause( $q ) { 819 return empty( $q ) || array_key_exists( 'terms', $q ) || array_key_exists( 'taxonomy', $q ) || array_key_exists( 'include_children', $q ) || array_key_exists( 'field', $q ) || array_key_exists( 'operator', $q ); 820 } 805 821 806 if ( ! empty( $where ) ) { 807 $where = ' AND ( ' . implode( " $this->relation ", $where ) . ' )'; 808 } else { 809 $where = ''; 822 /** 823 * Recursive-friendly query sanitizer. 824 * 825 * Ensures that each query-level clause has a 'relation' key, and that 826 * each first-order clause contains all the necessary keys from 827 * $defaults. 828 * 829 * @since 4.1.0 830 * 831 * @param array $query A tax_query query clause. 832 * @return array 833 */ 834 protected function sanitize_query( $query ) { 835 $sanitized_query = array(); 836 837 $defaults = array( 838 'taxonomy' => '', 839 'terms' => array(), 840 'include_children' => true, 841 'field' => 'term_id', 842 'operator' => 'IN', 843 ); 844 845 foreach ( $query as $key => $q ) { 846 if ( 'relation' === $key ) { 847 $sanitized_query['relation'] = $q; 848 } else if ( is_array( $q ) ) { 849 if ( ! $this->is_first_order_clause( $q ) ) { 850 $sanitized_query[] = $this->sanitize_query( $q ); 851 } else { 852 $sanitized_clause = array_merge( $defaults, $q ); 853 $sanitized_clause['terms'] = (array) $sanitized_clause['terms']; 854 $sanitized_query[] = $sanitized_clause; 855 856 // Keep a copy of the clause in the 857 // flat $queried_terms array, for use 858 // in WP_Query 859 if ( ! empty( $sanitized_clause['taxonomy'] ) && 'NOT IN' !== $sanitized_clause['operator'] ) { 860 $taxonomy = $sanitized_clause['taxonomy']; 861 if ( ! isset( $this->queried_terms[ $taxonomy ] ) ) { 862 $this->queried_terms[ $taxonomy ] = array(); 863 } 864 865 // Backward compatibility: Only store the first 866 // 'terms' and 'field' found for a given taxonomy 867 if ( isset( $sanitized_clause['terms'] ) && ! isset( $this->queried_terms[ $taxonomy ]['terms'] ) ) { 868 $this->queried_terms[ $taxonomy ]['terms'] = $sanitized_clause['terms']; 869 } 870 871 if ( isset( $sanitized_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) { 872 $this->queried_terms[ $taxonomy ]['field'] = $sanitized_clause['field']; 873 } 874 } 875 } 876 } 810 877 } 811 return compact( 'join', 'where' ); 878 879 return $sanitized_query; 812 880 } 813 881 814 882 /** -
src/wp-settings.php
diff --git src/wp-settings.php src/wp-settings.php index 9795971..5dca996 100644
wp_not_installed(); 111 111 // Load most of WordPress. 112 112 require( ABSPATH . WPINC . '/class-wp-walker.php' ); 113 113 require( ABSPATH . WPINC . '/class-wp-ajax-response.php' ); 114 require( ABSPATH . WPINC . '/class-wp-recursive-query.php' ); 114 115 require( ABSPATH . WPINC . '/formatting.php' ); 115 116 require( ABSPATH . WPINC . '/capabilities.php' ); 116 117 require( ABSPATH . WPINC . '/query.php' );