--- a/wp/wp-includes/class-wp-meta-query.php Mon Oct 14 18:06:33 2019 +0200
+++ b/wp/wp-includes/class-wp-meta-query.php Mon Oct 14 18:28:13 2019 +0200
@@ -99,7 +99,7 @@
*
* @since 3.2.0
* @since 4.2.0 Introduced support for naming query clauses by associative array keys.
- *
+ * @since 5.1.0 Introduced $compare_key clause parameter, which enables LIKE key matches.
*
* @param array $meta_query {
* Array of meta query clauses. When first-order clauses or sub-clauses use strings as
@@ -110,23 +110,26 @@
* @type array {
* Optional. An array of first-order clause parameters, or another fully-formed meta query.
*
- * @type string $key Meta key to filter by.
- * @type string $value Meta value to filter by.
- * @type string $compare MySQL operator used for comparing the $value. Accepts '=',
- * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',
- * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP',
- * 'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'.
- * Default is 'IN' when `$value` is an array, '=' otherwise.
- * @type string $type MySQL data type that the meta_value column will be CAST to for
- * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',
- * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'.
- * Default is 'CHAR'.
+ * @type string $key Meta key to filter by.
+ * @type string $compare_key MySQL operator used for comparing the $key. Accepts '=' and 'LIKE'.
+ * Default '='.
+ * @type string $value Meta value to filter by.
+ * @type string $compare MySQL operator used for comparing the $value. Accepts '=',
+ * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',
+ * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP',
+ * 'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'.
+ * Default is 'IN' when `$value` is an array, '=' otherwise.
+ * @type string $type MySQL data type that the meta_value column will be CAST to for
+ * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',
+ * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'.
+ * Default is 'CHAR'.
* }
* }
*/
public function __construct( $meta_query = false ) {
- if ( !$meta_query )
+ if ( ! $meta_query ) {
return;
+ }
if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
$this->relation = 'OR';
@@ -161,7 +164,7 @@
} elseif ( ! is_array( $query ) ) {
continue;
- // First-order clause.
+ // First-order clause.
} elseif ( $this->is_first_order_clause( $query ) ) {
if ( isset( $query['value'] ) && array() === $query['value'] ) {
unset( $query['value'] );
@@ -169,7 +172,7 @@
$clean_queries[ $key ] = $query;
- // Otherwise, it's a nested query, so we recurse.
+ // Otherwise, it's a nested query, so we recurse.
} else {
$cleaned_query = $this->sanitize_query( $query );
@@ -186,17 +189,17 @@
// Sanitize the 'relation' key provided in the query.
if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
$clean_queries['relation'] = 'OR';
- $this->has_or_relation = true;
+ $this->has_or_relation = true;
- /*
- * If there is only a single clause, call the relation 'OR'.
- * This value will not actually be used to join clauses, but it
- * simplifies the logic around combining key-only queries.
- */
+ /*
+ * If there is only a single clause, call the relation 'OR'.
+ * This value will not actually be used to join clauses, but it
+ * simplifies the logic around combining key-only queries.
+ */
} elseif ( 1 === count( $clean_queries ) ) {
$clean_queries['relation'] = 'OR';
- // Default to AND.
+ // Default to AND.
} else {
$clean_queries['relation'] = 'AND';
}
@@ -236,7 +239,7 @@
* the rest of the meta_query).
*/
$primary_meta_query = array();
- foreach ( array( 'key', 'compare', 'type' ) as $key ) {
+ foreach ( array( 'key', 'compare', 'type', 'compare_key' ) as $key ) {
if ( ! empty( $qv[ "meta_$key" ] ) ) {
$primary_meta_query[ $key ] = $qv[ "meta_$key" ];
}
@@ -275,16 +278,19 @@
* @return string MySQL type.
*/
public function get_cast_for_type( $type = '' ) {
- if ( empty( $type ) )
+ if ( empty( $type ) ) {
return 'CHAR';
+ }
$meta_type = strtoupper( $type );
- if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) )
+ if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) {
return 'CHAR';
+ }
- if ( 'NUMERIC' == $meta_type )
+ if ( 'NUMERIC' == $meta_type ) {
$meta_type = 'SIGNED';
+ }
return $meta_type;
}
@@ -333,7 +339,7 @@
*
* @since 3.1.0
*
- * @param array $clauses Array containing the query's JOIN and WHERE clauses.
+ * @param array $sql Array containing the query's JOIN and WHERE clauses.
* @param array $queries Array of meta queries.
* @param string $type Type of meta.
* @param string $primary_table Primary table.
@@ -364,7 +370,7 @@
* To keep $this->queries unaltered, pass a copy.
*/
$queries = $this->queries;
- $sql = $this->get_sql_for_query( $queries );
+ $sql = $this->get_sql_for_query( $queries );
if ( ! empty( $sql['where'] ) ) {
$sql['where'] = ' AND ' . $sql['where'];
@@ -404,7 +410,7 @@
$indent = '';
for ( $i = 0; $i < $depth; $i++ ) {
- $indent .= " ";
+ $indent .= ' ';
}
foreach ( $query as $key => &$clause ) {
@@ -426,7 +432,7 @@
}
$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
- // This is a subquery, so we recurse.
+ // This is a subquery, so we recurse.
} else {
$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
@@ -482,7 +488,7 @@
$sql_chunks = array(
'where' => array(),
- 'join' => array(),
+ 'join' => array(),
);
if ( isset( $clause['compare'] ) ) {
@@ -491,18 +497,39 @@
$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
}
- if ( ! in_array( $clause['compare'], array(
- '=', '!=', '>', '>=', '<', '<=',
- 'LIKE', 'NOT LIKE',
- 'IN', 'NOT IN',
- 'BETWEEN', 'NOT BETWEEN',
- 'EXISTS', 'NOT EXISTS',
- 'REGEXP', 'NOT REGEXP', 'RLIKE'
- ) ) ) {
+ if ( ! in_array(
+ $clause['compare'],
+ array(
+ '=',
+ '!=',
+ '>',
+ '>=',
+ '<',
+ '<=',
+ 'LIKE',
+ 'NOT LIKE',
+ 'IN',
+ 'NOT IN',
+ 'BETWEEN',
+ 'NOT BETWEEN',
+ 'EXISTS',
+ 'NOT EXISTS',
+ 'REGEXP',
+ 'NOT REGEXP',
+ 'RLIKE',
+ )
+ ) ) {
$clause['compare'] = '=';
}
- $meta_compare = $clause['compare'];
+ if ( isset( $clause['compare_key'] ) && 'LIKE' === strtoupper( $clause['compare_key'] ) ) {
+ $clause['compare_key'] = strtoupper( $clause['compare_key'] );
+ } else {
+ $clause['compare_key'] = '=';
+ }
+
+ $meta_compare = $clause['compare'];
+ $meta_compare_key = $clause['compare_key'];
// First build the JOIN clause, if one is required.
$join = '';
@@ -510,16 +537,21 @@
// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
$alias = $this->find_compatible_table_alias( $clause, $parent_query );
if ( false === $alias ) {
- $i = count( $this->table_aliases );
+ $i = count( $this->table_aliases );
$alias = $i ? 'mt' . $i : $this->meta_table;
// JOIN clauses for NOT EXISTS have their own syntax.
if ( 'NOT EXISTS' === $meta_compare ) {
$join .= " LEFT JOIN $this->meta_table";
$join .= $i ? " AS $alias" : '';
- $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
- // All other JOIN clauses.
+ if ( 'LIKE' === $meta_compare_key ) {
+ $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key LIKE %s )", '%' . $wpdb->esc_like( $clause['key'] ) . '%' );
+ } else {
+ $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
+ }
+
+ // All other JOIN clauses.
} else {
$join .= " INNER JOIN $this->meta_table";
$join .= $i ? " AS $alias" : '';
@@ -527,15 +559,15 @@
}
$this->table_aliases[] = $alias;
- $sql_chunks['join'][] = $join;
+ $sql_chunks['join'][] = $join;
}
// Save the alias to this clause, for future siblings to find.
$clause['alias'] = $alias;
// Determine the data type.
- $_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
- $meta_type = $this->get_cast_for_type( $_meta_type );
+ $_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
+ $meta_type = $this->get_cast_for_type( $_meta_type );
$clause['cast'] = $meta_type;
// Fallback for clause keys is the table alias. Key must be a string.
@@ -544,7 +576,7 @@
}
// Ensure unique clause keys, so none are overwritten.
- $iterator = 1;
+ $iterator = 1;
$clause_key_base = $clause_key;
while ( isset( $this->clauses[ $clause_key ] ) ) {
$clause_key = $clause_key_base . '-' . $iterator;
@@ -561,7 +593,11 @@
if ( 'NOT EXISTS' === $meta_compare ) {
$sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
} else {
- $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
+ if ( 'LIKE' === $meta_compare_key ) {
+ $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key LIKE %s", '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%' );
+ } else {
+ $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
+ }
}
}
@@ -578,36 +614,36 @@
}
switch ( $meta_compare ) {
- case 'IN' :
- case 'NOT IN' :
+ case 'IN':
+ case 'NOT IN':
$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
- $where = $wpdb->prepare( $meta_compare_string, $meta_value );
+ $where = $wpdb->prepare( $meta_compare_string, $meta_value );
break;
- case 'BETWEEN' :
- case 'NOT BETWEEN' :
+ case 'BETWEEN':
+ case 'NOT BETWEEN':
$meta_value = array_slice( $meta_value, 0, 2 );
- $where = $wpdb->prepare( '%s AND %s', $meta_value );
+ $where = $wpdb->prepare( '%s AND %s', $meta_value );
break;
- case 'LIKE' :
- case 'NOT LIKE' :
+ case 'LIKE':
+ case 'NOT LIKE':
$meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
- $where = $wpdb->prepare( '%s', $meta_value );
+ $where = $wpdb->prepare( '%s', $meta_value );
break;
// EXISTS with a value is interpreted as '='.
- case 'EXISTS' :
+ case 'EXISTS':
$meta_compare = '=';
- $where = $wpdb->prepare( '%s', $meta_value );
+ $where = $wpdb->prepare( '%s', $meta_value );
break;
// 'value' is ignored for NOT EXISTS.
- case 'NOT EXISTS' :
+ case 'NOT EXISTS':
$where = '';
break;
- default :
+ default:
$where = $wpdb->prepare( '%s', $meta_value );
break;
@@ -687,7 +723,7 @@
if ( 'OR' === $parent_query['relation'] ) {
$compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
- // Clauses joined by AND with "negative" operators share a join only if they also share a key.
+ // Clauses joined by AND with "negative" operators share a join only if they also share a key.
} elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
$compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
}
@@ -710,7 +746,7 @@
* @param array $parent_query Parent of $clause.
* @param object $this WP_Meta_Query object.
*/
- return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ;
+ return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this );
}
/**