diff --git a/adapters/travis.php b/adapters/travis.php index 7bbc966..7c66213 100644 --- a/adapters/travis.php +++ b/adapters/travis.php @@ -132,6 +132,7 @@ function es_wp_query_index_test_data() { "properties": { "name": { "type": "string", "index": "not_analyzed" }, "term_id": { "type": "long" }, + "term_taxonomy_id": { "type": "long" }, "slug": { "type": "string", "index": "not_analyzed" } } } @@ -297,10 +298,21 @@ function travis_es_verify_response_code( $response ) { if ( is_wp_error( $response ) ) { printf( "Message: %s\n", $response->get_error_message() ); } + printf( "Backtrace: %s\n", travis_es_debug_backtrace_summary() ); exit( 1 ); } } + function travis_es_debug_backtrace_summary() { + $backtrace = wp_debug_backtrace_summary( null, 0, false ); + foreach ( $backtrace as $k => $call ) { + if ( preg_match( '/PHPUnit_(TextUI_(Command|TestRunner)|Framework_(TestSuite|TestCase|TestResult))|ReflectionMethod|travis_es_(verify_response_code|debug_backtrace_summary)/', $call ) ) { + unset( $backtrace[ $k ] ); + } + } + return join( ', ', array_reverse( $backtrace ) ); + } + /** * Taken from SearchPress */ @@ -439,9 +451,10 @@ public function get_terms( $post ) { $terms = array(); foreach ( (array) $object_terms as $term ) { $terms[ $term->taxonomy ][] = array( - 'term_id' => $term->term_id, - 'slug' => $term->slug, - 'name' => $term->name, + 'term_id' => $term->term_id, + 'term_taxonomy_id' => $term->term_taxonomy_id, + 'slug' => $term->slug, + 'name' => $term->name, ); } diff --git a/class-es-wp-query-wrapper.php b/class-es-wp-query-wrapper.php index d221693..19a37d7 100644 --- a/class-es-wp-query-wrapper.php +++ b/class-es-wp-query-wrapper.php @@ -524,7 +524,7 @@ public function get_posts() { // Taxonomies if ( ! $this->is_singular ) { $this->parse_tax_query( $q ); - $this->tax_query = new ES_WP_Tax_Query( $this->tax_query ); + $this->tax_query = ES_WP_Tax_Query::get_from_tax_query( $this->tax_query ); $tax_filter = $this->tax_query->get_dsl( $this ); if ( false === $tax_filter ) { @@ -540,7 +540,7 @@ public function get_posts() { if ( empty($post_type) ) { // Do a fully inclusive search for currently registered post types of queried taxonomies $post_type = array(); - $taxonomies = wp_list_pluck( $this->tax_query->queries, 'taxonomy' ); + $taxonomies = array_keys( $this->tax_query->queried_terms ); foreach ( get_post_types( array( 'exclude_from_search' => false ) ) as $pt ) { $object_taxonomies = $pt === 'attachment' ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt ); if ( array_intersect( $taxonomies, $object_taxonomies ) ) @@ -551,59 +551,62 @@ public function get_posts() { elseif ( count( $post_type ) == 1 ) $post_type = $post_type[0]; - // @todo: no good way to do this in ES; workarounds? $post_status_join = true; } elseif ( in_array('attachment', (array) $post_type) ) { - // @todo: no good way to do this in ES; workarounds? $post_status_join = true; } } - // Back-compat - if ( ! empty( $this->tax_query->queries ) ) { - $tax_query_in_and = wp_list_filter( $this->tax_query->queries, array( 'operator' => 'NOT IN' ), 'NOT' ); - if ( !empty( $tax_query_in_and ) ) { - if ( !isset( $q['taxonomy'] ) ) { - foreach ( $tax_query_in_and as $a_tax_query ) { - if ( !in_array( $a_tax_query['taxonomy'], array( 'category', 'post_tag' ) ) ) { - $q['taxonomy'] = $a_tax_query['taxonomy']; - if ( 'slug' == $a_tax_query['field'] ) - $q['term'] = $a_tax_query['terms'][0]; - else - $q['term_id'] = $a_tax_query['terms'][0]; + /* + * Ensure that 'taxonomy', 'term', 'term_id', 'cat', and + * 'category_name' vars are set for backward compatibility. + */ + if ( ! empty( $this->tax_query->queried_terms ) ) { - break; - } + /* + * Set 'taxonomy', 'term', and 'term_id' to the + * first taxonomy other than 'post_tag' or 'category'. + */ + if ( ! isset( $q['taxonomy'] ) ) { + foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) { + if ( empty( $queried_items['terms'][0] ) ) { + continue; } - } - $cat_query = wp_list_filter( $tax_query_in_and, array( 'taxonomy' => 'category' ) ); - if ( ! empty( $cat_query ) ) { - $cat_query = reset( $cat_query ); + if ( ! in_array( $queried_taxonomy, array( 'category', 'post_tag' ) ) ) { + $q['taxonomy'] = $queried_taxonomy; - if ( ! empty( $cat_query['terms'][0] ) ) { - $the_cat = get_term_by( $cat_query['field'], $cat_query['terms'][0], 'category' ); - if ( $the_cat ) { - $this->set( 'cat', $the_cat->term_id ); - $this->set( 'category_name', $the_cat->slug ); + if ( 'slug' === $queried_items['field'] ) { + $q['term'] = $queried_items['terms'][0]; + } else { + $q['term_id'] = $queried_items['terms'][0]; } - unset( $the_cat ); } } - unset( $cat_query ); + } - $tag_query = wp_list_filter( $tax_query_in_and, array( 'taxonomy' => 'post_tag' ) ); - if ( ! empty( $tag_query ) ) { - $tag_query = reset( $tag_query ); + // 'cat', 'category_name', 'tag_id' + foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) { + if ( empty( $queried_items['terms'][0] ) ) { + continue; + } + + if ( 'category' === $queried_taxonomy ) { + $the_cat = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'category' ); + if ( $the_cat ) { + $this->set( 'cat', $the_cat->term_id ); + $this->set( 'category_name', $the_cat->slug ); + } + unset( $the_cat ); + } - if ( ! empty( $tag_query['terms'][0] ) ) { - $the_tag = get_term_by( $tag_query['field'], $tag_query['terms'][0], 'post_tag' ); - if ( $the_tag ) - $this->set( 'tag_id', $the_tag->term_id ); - unset( $the_tag ); + if ( 'post_tag' === $queried_taxonomy ) { + $the_tag = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'post_tag' ); + if ( $the_tag ) { + $this->set( 'tag_id', $the_tag->term_id ); } + unset( $the_tag ); } - unset( $tag_query ); } } @@ -1365,4 +1368,12 @@ public static function dsl_match( $field, $value, $args = array() ) { public static function dsl_multi_match( $fields, $query, $args = array() ) { return array( 'multi_match' => array_merge( array( 'query' => $query, 'fields' => (array) $fields ), $args ) ); } -} \ No newline at end of file + + public static function dsl_all_terms( $field, $values ) { + $queries = array(); + foreach ( $values as $value ) { + $queries[] = array( 'term' => array( $field => $value ) ); + } + return array( 'bool' => array( 'must' => $queries ) ); + } +} diff --git a/class-es-wp-tax-query.php b/class-es-wp-tax-query.php index f92c2ca..cd53948 100644 --- a/class-es-wp-tax-query.php +++ b/class-es-wp-tax-query.php @@ -5,9 +5,27 @@ */ class ES_WP_Tax_Query extends WP_Tax_Query { - public function __construct( $tax_query ) { - $this->relation = $tax_query->relation; - $this->queries = $tax_query->queries; + /** + * Some object which extends ES_WP_Query_Wrapper. + * + * @var ES_WP_Query_Wrapper + */ + protected $es_query; + + public static function get_from_tax_query( $tax_query ) { + $q = new ES_WP_Tax_Query( $tax_query->queries ); + $q->relation = $tax_query->relation; + return $q; + } + + /** + * Get a (light) ES filter that will always produce no results. This allows + * individual tax query clauses to fail without breaking the rest of them. + * + * @return array ES term query for post_id:0. + */ + protected function get_no_results_clause() { + return $this->es_query->dsl_terms( $this->es_query->es_map( 'post_id' ), 0 ); } /** @@ -15,84 +33,176 @@ public function __construct( $tax_query ) { * * @access public * - * @return array + * @param object $es_query Any object which extends ES_WP_Query_Wrapper. + * @param string $type Type of meta. Currently, only 'post' is supported. + * @return array ES filters */ public function get_dsl( $es_query ) { - global $wpdb; - - $join = ''; - $filter = array(); - $count = count( $this->queries ); + $this->es_query = $es_query; - foreach ( $this->queries as $index => $query ) { - $filter_options = array(); - $current_filter = null; + $filters = $this->get_dsl_clauses(); - $this->clean_query( $query ); + return apply_filters_ref_array( 'es_wp_tax_query_dsl', array( $filters, $this->queries, $this->es_query ) ); + } - if ( is_wp_error( $query ) ) - return false; + /** + * Generate ES Filter clauses to be appended to a main query. + * + * Called by the public {@see ES_WP_Meta_Query::get_dsl()}, this method + * is abstracted out to maintain parity with the other Query classes. + * + * @access protected + * + * @return array + */ + protected function get_dsl_clauses() { + /* + * $queries are passed by reference to + * `ES_WP_Meta_Query::get_dsl_for_query()` for recursion. To keep + * $this->queries unaltered, pass a copy. + */ + $queries = $this->queries; + return $this->get_dsl_for_query( $queries ); + } - if ( 'AND' == $query['operator'] ) { - $filter_options = array( 'execution' => 'and' ); + /** + * Generate ES filters for a single query array. + * + * If nested subqueries are found, this method recurses the tree to produce + * the properly nested DSL. + * + * @access protected + * + * @param array $query Query to parse, passed by reference. + * @return boolarray Array containing nested ES filter clauses on success or + * false on error. + */ + protected function get_dsl_for_query( &$query ) { + $filters = array(); + + foreach ( $query as $key => &$clause ) { + if ( 'relation' === $key ) { + $relation = $query['relation']; + } elseif ( is_array( $clause ) ) { + if ( $this->is_first_order_clause( $clause ) ) { + // This is a first-order clause. + $filters[] = $this->get_dsl_for_clause( $clause, $query ); + } else { + // This is a subquery, so we recurse. + $filters[] = $this->get_dsl_for_query( $clause ); + } } + } - if ( 'IN' == $query['operator'] ) { + // Filter to remove empties. + $filters = array_filter( $filters ); - if ( empty( $query['terms'] ) ) { - if ( 'OR' == $this->relation ) { - if ( ( $index + 1 === $count ) && empty( $filter ) ) - return false; - continue; - } else { - return false; - } - } + if ( empty( $relation ) ) { + $relation = 'and'; + } + + if ( count( $filters ) > 1 ) { + $filters = array( strtolower( $relation ) => $filters ); + } elseif ( ! empty( $filters ) ) { + $filters = reset( $filters ); + } - } elseif ( 'NOT IN' == $query['operator'] ) { + return $filters; + } - if ( empty( $query['terms'] ) ) - continue; + /** + * Generate ES filter clauses for a first-order query clause. + * + * "First-order" means that it's an array with a 'key' or 'value'. + * + * @access public + * + * @param array $clause Query clause, passed by reference. + * @param array $query Parent query array. + * @return bool|array ES filter clause on success, or false on error. + */ + public function get_dsl_for_clause( &$clause, $query ) { + $current_filter = null; - } elseif ( 'AND' == $query['operator'] ) { + $this->clean_query( $clause ); - if ( empty( $query['terms'] ) ) - continue; + if ( is_wp_error( $clause ) ) { + return $this->get_no_results_clause(); + } + // If the comparison is EXISTS or NOT EXISTS, handle that first since + // it's quick and easy. + if ( 'EXISTS' == $clause['operator'] || 'NOT EXISTS' == $clause['operator'] ) { + if ( empty( $clause['taxonomy'] ) ) { + return $this->get_no_results_clause(); } - switch ( $query['field'] ) { - case 'slug' : - case 'name' : - $terms = array_map( 'sanitize_title_for_query', array_values( $query['terms'] ) ); - $current_filter = $es_query::dsl_terms( $es_query->tax_map( $query['taxonomy'], 'term_' . $query['field'] ), $terms, $filter_options ); - break; - - case 'term_taxonomy_id' : - // This will likely not be hit, as these were probably turned into term_ids. However, by - // returning false to the 'es_use_mysql_for_term_taxonomy_id' filter, you disable that. - $current_filter = $es_query::dsl_terms( $es_query->tax_map( $query['taxonomy'], 'term_tt_id' ), $query['terms'], $filter_options ); - break; - - default : - $terms = array_map( 'absint', array_values( $query['terms'] ) ); - $current_filter = $es_query::dsl_terms( $es_query->tax_map( $query['taxonomy'], 'term_id' ), $terms, $filter_options ); - break; + if ( 'EXISTS' == $clause['operator'] ) { + return $this->es_query->dsl_exists( $this->es_query->tax_map( $clause['taxonomy'], 'term_id' ) ); + } elseif ( 'NOT EXISTS' == $clause['operator'] ) { + return $this->es_query->dsl_missing( $this->es_query->tax_map( $clause['taxonomy'], 'term_id' ) ); } + } + + if ( 'AND' == $clause['operator'] ) { + $terms_method = array( $this->es_query, 'dsl_all_terms' ); + } else { + $terms_method = array( $this->es_query, 'dsl_terms' ); + } - if ( 'NOT IN' == $query['operator'] ) { - $filter[] = array( 'not' => $current_filter ); - } else { - $filter[] = $current_filter; + if ( empty( $clause['terms'] ) ) { + if ( 'NOT IN' == $clause['operator'] || 'AND' == $clause['operator'] ) { + return array(); + } elseif ( 'IN' == $clause['operator'] ) { + return $this->get_no_results_clause(); } } - if ( 1 == count( $filter ) ) { - return reset( $filter ); - } elseif ( ! empty( $filter ) ) { - return array( strtolower( $this->relation ) => $filter ); + switch ( $clause['field'] ) { + case 'slug' : + case 'name' : + foreach ( $clause['terms'] as &$term ) { + /* + * 0 is the $term_id parameter. We don't have a term ID yet, but it doesn't + * matter because `sanitize_term_field()` ignores the $term_id param when the + * context is 'db'. + */ + $term = sanitize_term_field( $clause['field'], $term, 0, $clause['taxonomy'], 'db' ); + } + $current_filter = call_user_func( $terms_method, $this->es_query->tax_map( $clause['taxonomy'], 'term_' . $clause['field'] ), $clause['terms'] ); + break; + + case 'term_taxonomy_id' : + if ( ! empty( $clause['taxonomy'] ) ) { + $current_filter = call_user_func( $terms_method, $this->es_query->tax_map( $clause['taxonomy'], 'term_tt_id' ), $clause['terms'] ); + } else { + $matches = array(); + foreach ( $clause['terms'] as &$term ) { + $matches[] = $this->es_query->dsl_multi_match( $this->es_query->tax_map( '*', 'term_tt_id' ), $term ); + } + if ( count( $matches ) > 1 ) { + $current_filter = array( + 'bool' => array( + ( 'AND' == $clause['operator'] ? 'must' : 'should' ) => $matches, + ), + ); + } else { + $current_filter = reset( $matches ); + } + } + + break; + + default : + $terms = array_map( 'absint', array_values( $clause['terms'] ) ); + $current_filter = call_user_func( $terms_method, $this->es_query->tax_map( $clause['taxonomy'], 'term_id' ), $terms ); + break; + } + + if ( 'NOT IN' == $clause['operator'] ) { + return array( 'not' => $current_filter ); } else { - return array(); + return $current_filter; } } @@ -106,7 +216,15 @@ public function get_dsl( $es_query ) { * @param array &$query The single query */ private function clean_query( &$query ) { - if ( empty( $query['taxonomy'] ) || ! taxonomy_exists( $query['taxonomy'] ) ) { + if ( empty( $query['taxonomy'] ) ) { + if ( 'term_taxonomy_id' !== $query['field'] ) { + $query = new WP_Error( 'Invalid taxonomy' ); + return; + } + + // so long as there are shared terms, include_children requires that a taxonomy is set + $query['include_children'] = false; + } elseif ( ! taxonomy_exists( $query['taxonomy'] ) ) { $query = new WP_Error( 'Invalid taxonomy' ); return; } @@ -116,8 +234,9 @@ private function clean_query( &$query ) { if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) { $this->transform_query( $query, 'term_id' ); - if ( is_wp_error( $query ) ) + if ( is_wp_error( $query ) ) { return; + } $children = array(); foreach ( $query['terms'] as $term ) { @@ -129,7 +248,7 @@ private function clean_query( &$query ) { // If we have a term_taxonomy_id, use mysql, as that's almost certainly not stored in ES. // However, you can override this. - if ( 'term_taxonomy_id' == $query['field'] ) { + if ( 'term_taxonomy_id' == $query['field'] && ! empty( $query['taxonomy'] ) ) { if ( apply_filters( 'es_use_mysql_for_term_taxonomy_id', true ) ) { $this->transform_query( $query, 'term_id' ); } @@ -145,11 +264,13 @@ private function clean_query( &$query ) { public function transform_query( &$query, $resulting_field ) { global $wpdb; - if ( empty( $query['terms'] ) ) + if ( empty( $query['terms'] ) ) { return; + } - if ( $query['field'] == $resulting_field ) + if ( $query['field'] == $resulting_field ) { return; + } $resulting_field = sanitize_key( $resulting_field ); diff --git a/tests/query/post.php b/tests/query/post.php index ed8acf7..8c47dd3 100755 --- a/tests/query/post.php +++ b/tests/query/post.php @@ -262,94 +262,4 @@ function test_taxonomy_empty_or() { $posts = $query->get_posts(); $this->assertEquals( 0 , count( $posts ) ); } - - function test_taxonomy_include_children() { - $cat_a = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'Australia' ) ); - $cat_b = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'Sydney', 'parent' => $cat_a ) ); - $cat_c = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'East Syndney', 'parent' => $cat_b ) ); - $cat_d = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'West Syndney', 'parent' => $cat_b ) ); - - $post_a = $this->factory->post->create( array( 'post_category' => array( $cat_a ) ) ); - $post_b = $this->factory->post->create( array( 'post_category' => array( $cat_b ) ) ); - $post_c = $this->factory->post->create( array( 'post_category' => array( $cat_c ) ) ); - $post_d = $this->factory->post->create( array( 'post_category' => array( $cat_d ) ) ); - - es_wp_query_index_test_data(); - - $posts = es_get_posts( array( - 'tax_query' => array( - array( - 'taxonomy' => 'category', - 'field' => 'id', - 'terms' => array( $cat_a ), - ) - ) - ) ); - - $this->assertEquals( 4 , count( $posts ) ); - - $posts = es_get_posts( array( - 'tax_query' => array( - array( - 'taxonomy' => 'category', - 'field' => 'id', - 'terms' => array( $cat_a ), - 'include_children' => false - ) - ) - ) ); - - $this->assertEquals( 1 , count( $posts ) ); - - $posts = es_get_posts( array( - 'tax_query' => array( - array( - 'taxonomy' => 'category', - 'field' => 'id', - 'terms' => array( $cat_b ), - ) - ) - ) ); - - $this->assertEquals( 3 , count( $posts ) ); - - $posts = es_get_posts( array( - 'tax_query' => array( - array( - 'taxonomy' => 'category', - 'field' => 'id', - 'terms' => array( $cat_b ), - 'include_children' => false - ) - ) - ) ); - - $this->assertEquals( 1 , count( $posts ) ); - - $posts = es_get_posts( array( - 'tax_query' => array( - array( - 'taxonomy' => 'category', - 'field' => 'id', - 'terms' => array( $cat_c ), - ) - ) - ) ); - - $this->assertEquals( 1 , count( $posts ) ); - - $posts = es_get_posts( array( - 'tax_query' => array( - array( - 'taxonomy' => 'category', - 'field' => 'id', - 'terms' => array( $cat_c ), - 'include_children' => false - ) - ) - ) ); - - $this->assertEquals( 1 , count( $posts ) ); - } - } \ No newline at end of file diff --git a/tests/query/taxQuery.php b/tests/query/taxQuery.php new file mode 100644 index 0000000..ac9791e --- /dev/null +++ b/tests/query/taxQuery.php @@ -0,0 +1,1370 @@ +factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + public function test_tax_query_single_query_single_term_field_name() { + $t = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'Foo' ), + 'field' => 'name', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + /** + * @ticket 27810 + */ + public function test_field_name_should_work_for_names_with_spaces() { + register_taxonomy( 'wptests_tax', 'post' ); + + $t = $this->factory->term->create( array( + 'taxonomy' => 'wptests_tax', + 'slug' => 'foo', + 'name' => 'Foo Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_object_terms( $p1, $t, 'wptests_tax' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => 'wptests_tax', + 'terms' => array( 'Foo Bar' ), + 'field' => 'name', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + public function test_tax_query_single_query_single_term_field_term_taxonomy_id() { + $t = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + $tt_ids = wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => $tt_ids, + 'field' => 'term_taxonomy_id', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + public function test_tax_query_single_query_single_term_field_term_id() { + $t = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( $t ), + 'field' => 'term_id', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + public function test_tax_query_single_query_single_term_operator_in() { + $t = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + 'operator' => 'IN', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + public function test_tax_query_single_query_single_term_operator_not_in() { + $t = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + 'operator' => 'NOT IN', + ), + ), + ) ); + + $this->assertEquals( array( $p2 ), $q->posts ); + } + + public function test_tax_query_single_query_single_term_operator_and() { + $t = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + 'operator' => 'AND', + ), + ), + ) ); + + $this->assertEquals( array( $p1 ), $q->posts ); + } + + public function test_tax_query_single_query_multiple_terms_operator_in() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t1, 'category' ); + wp_set_post_terms( $p2, $t2, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo', 'bar' ), + 'field' => 'slug', + 'operator' => 'IN', + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p2 ), $q->posts ); + } + + public function test_tax_query_single_query_multiple_terms_operator_not_in() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t1, 'category' ); + wp_set_post_terms( $p2, $t2, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo', 'bar' ), + 'field' => 'slug', + 'operator' => 'NOT IN', + ), + ), + ) ); + + $this->assertEquals( array( $p3 ), $q->posts ); + } + + /** + * @ticket 18105 + */ + public function test_tax_query_single_query_multiple_queries_operator_not_in() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_post_terms( $p1, $t1, 'category' ); + wp_set_post_terms( $p2, $t2, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'AND', + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + 'operator' => 'NOT IN', + ), + array( + 'taxonomy' => 'category', + 'terms' => array( 'bar' ), + 'field' => 'slug', + 'operator' => 'NOT IN', + ), + ), + ) ); + + $this->assertEquals( array( $p3 ), $q->posts ); + } + + public function test_tax_query_single_query_multiple_terms_operator_and() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, $t1, 'category' ); + wp_set_object_terms( $p2, array( $t1, $t2 ), 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo', 'bar' ), + 'field' => 'slug', + 'operator' => 'AND', + ), + ), + ) ); + + $this->assertEquals( array( $p2 ), $q->posts ); + } + + /** + * @ticket 29181 + */ + public function test_tax_query_operator_not_exists() { + register_taxonomy( 'wptests_tax1', 'post' ); + register_taxonomy( 'wptests_tax2', 'post' ); + + $t1 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax1' ) ); + $t2 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $t1 ), 'wptests_tax1' ); + wp_set_object_terms( $p2, array( $t2 ), 'wptests_tax2' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'orderby' => 'ID', + 'order' => 'ASC', + 'tax_query' => array( + array( + 'taxonomy' => 'wptests_tax2', + 'operator' => 'NOT EXISTS', + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p3 ), $q->posts ); + } + + /** + * @ticket 29181 + */ + public function test_tax_query_operator_exists() { + register_taxonomy( 'wptests_tax1', 'post' ); + register_taxonomy( 'wptests_tax2', 'post' ); + + $t1 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax1' ) ); + $t2 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $t1 ), 'wptests_tax1' ); + wp_set_object_terms( $p2, array( $t2 ), 'wptests_tax2' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'orderby' => 'ID', + 'order' => 'ASC', + 'tax_query' => array( + array( + 'taxonomy' => 'wptests_tax2', + 'operator' => 'EXISTS', + ), + ), + ) ); + + $this->assertEqualSets( array( $p2 ), $q->posts ); + } + + /** + * @ticket 29181 + */ + public function test_tax_query_operator_exists_should_ignore_terms() { + register_taxonomy( 'wptests_tax1', 'post' ); + register_taxonomy( 'wptests_tax2', 'post' ); + + $t1 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax1' ) ); + $t2 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $t1 ), 'wptests_tax1' ); + wp_set_object_terms( $p2, array( $t2 ), 'wptests_tax2' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'orderby' => 'ID', + 'order' => 'ASC', + 'tax_query' => array( + array( + 'taxonomy' => 'wptests_tax2', + 'operator' => 'EXISTS', + 'terms' => array( 'foo', 'bar' ), + ), + ), + ) ); + + $this->assertEqualSets( array( $p2 ), $q->posts ); + } + + /** + * @ticket 29181 + */ + public function test_tax_query_operator_exists_with_no_taxonomy() { + register_taxonomy( 'wptests_tax1', 'post' ); + register_taxonomy( 'wptests_tax2', 'post' ); + + $t1 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax1' ) ); + $t2 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $t1 ), 'wptests_tax1' ); + wp_set_object_terms( $p2, array( $t2 ), 'wptests_tax2' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'orderby' => 'ID', + 'order' => 'ASC', + 'tax_query' => array( + array( + 'operator' => 'EXISTS', + ), + ), + ) ); + + $this->assertEmpty( $q->posts ); + } + + public function test_tax_query_multiple_queries_relation_and() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, $t1, 'category' ); + wp_set_object_terms( $p2, array( $t1, $t2 ), 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'AND', + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + ), + array( + 'taxonomy' => 'category', + 'terms' => array( 'bar' ), + 'field' => 'slug', + ), + ), + ) ); + + $this->assertEquals( array( $p2 ), $q->posts ); + } + + public function test_tax_query_multiple_queries_relation_or() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, $t1, 'category' ); + wp_set_object_terms( $p2, array( $t1, $t2 ), 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'taxonomy' => 'category', + 'terms' => array( 'foo' ), + 'field' => 'slug', + ), + array( + 'taxonomy' => 'category', + 'terms' => array( 'bar' ), + 'field' => 'slug', + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p2 ), $q->posts ); + } + + public function test_tax_query_multiple_queries_different_taxonomies() { + $t1 = $this->factory->term->create( array( + 'taxonomy' => 'post_tag', + 'slug' => 'foo', + 'name' => 'Foo', + ) ); + $t2 = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + 'name' => 'Bar', + ) ); + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, $t1, 'post_tag' ); + wp_set_object_terms( $p2, $t2, 'category' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'foo' ), + 'field' => 'slug', + ), + array( + 'taxonomy' => 'category', + 'terms' => array( 'bar' ), + 'field' => 'slug', + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p2 ), $q->posts ); + } + + /** + * @ticket 29738 + */ + public function test_tax_query_two_nested_queries() { + register_taxonomy( 'foo', 'post' ); + register_taxonomy( 'bar', 'post' ); + + $foo_term_1 = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $foo_term_2 = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $bar_term_1 = $this->factory->term->create( array( + 'taxonomy' => 'bar', + ) ); + $bar_term_2 = $this->factory->term->create( array( + 'taxonomy' => 'bar', + ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $foo_term_1 ), 'foo' ); + wp_set_object_terms( $p1, array( $bar_term_1 ), 'bar' ); + wp_set_object_terms( $p2, array( $foo_term_2 ), 'foo' ); + wp_set_object_terms( $p2, array( $bar_term_2 ), 'bar' ); + wp_set_object_terms( $p3, array( $foo_term_1 ), 'foo' ); + wp_set_object_terms( $p3, array( $bar_term_2 ), 'bar' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'relation' => 'AND', + array( + 'taxonomy' => 'foo', + 'terms' => array( $foo_term_1 ), + 'field' => 'term_id', + ), + array( + 'taxonomy' => 'bar', + 'terms' => array( $bar_term_1 ), + 'field' => 'term_id', + ), + ), + array( + 'relation' => 'AND', + array( + 'taxonomy' => 'foo', + 'terms' => array( $foo_term_2 ), + 'field' => 'term_id', + ), + array( + 'taxonomy' => 'bar', + 'terms' => array( $bar_term_2 ), + 'field' => 'term_id', + ), + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p2 ), $q->posts ); + } + + /** + * @ticket 29738 + */ + public function test_tax_query_one_nested_query_one_first_order_query() { + register_taxonomy( 'foo', 'post' ); + register_taxonomy( 'bar', 'post' ); + + $foo_term_1 = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $foo_term_2 = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $bar_term_1 = $this->factory->term->create( array( + 'taxonomy' => 'bar', + ) ); + $bar_term_2 = $this->factory->term->create( array( + 'taxonomy' => 'bar', + ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $foo_term_1 ), 'foo' ); + wp_set_object_terms( $p1, array( $bar_term_1 ), 'bar' ); + wp_set_object_terms( $p2, array( $foo_term_2 ), 'foo' ); + wp_set_object_terms( $p2, array( $bar_term_2 ), 'bar' ); + wp_set_object_terms( $p3, array( $foo_term_1 ), 'foo' ); + wp_set_object_terms( $p3, array( $bar_term_2 ), 'bar' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'taxonomy' => 'foo', + 'terms' => array( $foo_term_2 ), + 'field' => 'term_id', + ), + array( + 'relation' => 'AND', + array( + 'taxonomy' => 'foo', + 'terms' => array( $foo_term_1 ), + 'field' => 'term_id', + ), + array( + 'taxonomy' => 'bar', + 'terms' => array( $bar_term_1 ), + 'field' => 'term_id', + ), + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p2 ), $q->posts ); + } + + /** + * @ticket 29738 + */ + public function test_tax_query_one_double_nested_query_one_first_order_query() { + register_taxonomy( 'foo', 'post' ); + register_taxonomy( 'bar', 'post' ); + + $foo_term_1 = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $foo_term_2 = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $bar_term_1 = $this->factory->term->create( array( + 'taxonomy' => 'bar', + ) ); + $bar_term_2 = $this->factory->term->create( array( + 'taxonomy' => 'bar', + ) ); + + $p1 = $this->factory->post->create(); + $p2 = $this->factory->post->create(); + $p3 = $this->factory->post->create(); + $p4 = $this->factory->post->create(); + + wp_set_object_terms( $p1, array( $foo_term_1 ), 'foo' ); + wp_set_object_terms( $p1, array( $bar_term_1 ), 'bar' ); + wp_set_object_terms( $p2, array( $foo_term_2 ), 'foo' ); + wp_set_object_terms( $p2, array( $bar_term_2 ), 'bar' ); + wp_set_object_terms( $p3, array( $foo_term_1 ), 'foo' ); + wp_set_object_terms( $p3, array( $bar_term_2 ), 'bar' ); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'taxonomy' => 'foo', + 'terms' => array( $foo_term_2 ), + 'field' => 'term_id', + ), + array( + 'relation' => 'AND', + array( + 'taxonomy' => 'foo', + 'terms' => array( $foo_term_1 ), + 'field' => 'term_id', + ), + array( + 'relation' => 'OR', + array( + 'taxonomy' => 'bar', + 'terms' => array( $bar_term_1 ), + 'field' => 'term_id', + ), + array( + 'taxonomy' => 'bar', + 'terms' => array( $bar_term_2 ), + 'field' => 'term_id', + ), + ), + ), + ), + ) ); + + $this->assertEqualSets( array( $p1, $p2, $p3 ), $q->posts ); + } + + /** + * @ticket 20604 + */ + public function test_tax_query_relation_or_both_clauses_empty_terms() { + // An empty tax query should return an empty array, not all posts. + + $this->factory->post->create_many( 2 ); + es_wp_query_index_test_data(); + + $query = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'taxonomy' => 'post_tag', + 'field' => 'id', + 'terms' => false, + 'operator' => 'IN' + ), + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => false, + 'operator' => 'IN' + ), + ) + ) ); + + $posts = $query->get_posts(); + $this->assertEquals( 0 , count( $posts ) ); + } + + /** + * @ticket 20604 + */ + public function test_tax_query_relation_or_one_clause_empty_terms() { + // An empty tax query should return an empty array, not all posts. + + $this->factory->post->create_many( 2 ); + es_wp_query_index_test_data(); + + $query = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + 'tax_query' => array( + 'relation' => 'OR', + array( + 'taxonomy' => 'post_tag', + 'field' => 'id', + 'terms' => array( 'foo' ), + 'operator' => 'IN' + ), + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => false, + 'operator' => 'IN' + ), + ) + ) ); + + $posts = $query->get_posts(); + $this->assertEquals( 0 , count( $posts ) ); + } + + public function test_tax_query_include_children() { + $cat_a = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'Australia' ) ); + $cat_b = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'Sydney', 'parent' => $cat_a ) ); + $cat_c = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'East Syndney', 'parent' => $cat_b ) ); + $cat_d = $this->factory->term->create( array( 'taxonomy' => 'category', 'name' => 'West Syndney', 'parent' => $cat_b ) ); + + $post_a = $this->factory->post->create( array( 'post_category' => array( $cat_a ) ) ); + $post_b = $this->factory->post->create( array( 'post_category' => array( $cat_b ) ) ); + $post_c = $this->factory->post->create( array( 'post_category' => array( $cat_c ) ) ); + $post_d = $this->factory->post->create( array( 'post_category' => array( $cat_d ) ) ); + es_wp_query_index_test_data(); + + $posts = es_get_posts( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => array( $cat_a ), + ) + ) + ) ); + + $this->assertEquals( 4 , count( $posts ) ); + + $posts = es_get_posts( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => array( $cat_a ), + 'include_children' => false + ) + ) + ) ); + + $this->assertEquals( 1 , count( $posts ) ); + + $posts = es_get_posts( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => array( $cat_b ), + ) + ) + ) ); + + $this->assertEquals( 3 , count( $posts ) ); + + $posts = es_get_posts( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => array( $cat_b ), + 'include_children' => false + ) + ) + ) ); + + $this->assertEquals( 1 , count( $posts ) ); + + $posts = es_get_posts( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => array( $cat_c ), + ) + ) + ) ); + + $this->assertEquals( 1 , count( $posts ) ); + + $posts = es_get_posts( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'field' => 'id', + 'terms' => array( $cat_c ), + 'include_children' => false + ) + ) + ) ); + + $this->assertEquals( 1 , count( $posts ) ); + } + + public function test_tax_query_taxonomy_with_attachments() { + $q = new ES_WP_Query(); + + // This line deviates from core's unit tests. See + // {@link https://core.trac.wordpress.org/ticket/35995}. + register_taxonomy_for_object_type( 'post_tag', 'attachment' ); + $tag_id = $this->factory->term->create( array( 'slug' => rand_str(), 'name' => rand_str() ) ); + $image_id = $this->factory->attachment->create_object( 'image.jpg', 0, array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment' + ) ); + wp_set_object_terms( $image_id, $tag_id, 'post_tag' ); + es_wp_query_index_test_data(); + + $posts = $q->query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'field' => 'term_id', + 'terms' => array( $tag_id ) + ) + ) + ) ); + + $this->assertEquals( array( $image_id ), $posts ); + } + + public function test_tax_query_no_taxonomy() { + $cat_id = $this->factory->category->create( array( 'name' => 'alpha' ) ); + $this->factory->post->create( array( 'post_title' => 'alpha', 'post_category' => array( $cat_id ) ) ); + es_wp_query_index_test_data(); + + $response1 = new ES_WP_Query( array( + 'tax_query' => array( + array( 'terms' => array( $cat_id ) ) + ) + ) ); + $this->assertEmpty( $response1->posts ); + + $response2 = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'taxonomy' => 'category', + 'terms' => array( $cat_id ) + ) + ) + ) ); + $this->assertNotEmpty( $response2->posts ); + + $term = get_category( $cat_id ); + $response3 = new ES_WP_Query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'tax_query' => array( + array( + 'field' => 'term_taxonomy_id', + 'terms' => array( $term->term_taxonomy_id ) + ) + ) + ) ); + $this->assertNotEmpty( $response3->posts ); + } + + public function test_term_taxonomy_id_field_no_taxonomy() { + $q = new ES_WP_Query(); + + $posts = $this->factory->post->create_many( 5 ); + + $cats = $tags = array(); + + // need term_taxonomy_ids in addition to term_ids, so no factory + for ( $i = 0; $i < 5; $i++ ) { + $cats[$i] = wp_insert_term( 'category-' . $i , 'category' ); + $tags[$i] = wp_insert_term( 'tag-' . $i, 'post_tag' ); + + // post 0 gets all terms + wp_set_object_terms( $posts[0], array( $cats[$i]['term_id'] ), 'category', true ); + wp_set_object_terms( $posts[0], array( $tags[$i]['term_id'] ), 'post_tag', true ); + } + + wp_set_object_terms( $posts[1], array( $cats[0]['term_id'], $cats[2]['term_id'], $cats[4]['term_id'] ), 'category' ); + wp_set_object_terms( $posts[1], array( $tags[0]['term_id'], $tags[2]['term_id'], $cats[4]['term_id'] ), 'post_tag' ); + + wp_set_object_terms( $posts[2], array( $cats[1]['term_id'], $cats[3]['term_id'] ), 'category' ); + wp_set_object_terms( $posts[2], array( $tags[1]['term_id'], $tags[3]['term_id'] ), 'post_tag' ); + + wp_set_object_terms( $posts[3], array( $cats[0]['term_id'], $cats[2]['term_id'], $cats[4]['term_id'] ), 'category' ); + wp_set_object_terms( $posts[3], array( $tags[1]['term_id'], $tags[3]['term_id'] ), 'post_tag' ); + es_wp_query_index_test_data(); + + $results1 = $q->query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'orderby' => 'ID', + 'order' => 'ASC', + 'tax_query' => array( + 'relation' => 'OR', + array( + 'field' => 'term_taxonomy_id', + 'terms' => array( $cats[0]['term_taxonomy_id'], $cats[2]['term_taxonomy_id'], $cats[4]['term_taxonomy_id'], $tags[0]['term_taxonomy_id'], $tags[2]['term_taxonomy_id'], $cats[4]['term_taxonomy_id'] ), + 'operator' => 'AND', + 'include_children' => false, + ), + array( + 'field' => 'term_taxonomy_id', + 'terms' => array( $cats[1]['term_taxonomy_id'], $cats[3]['term_taxonomy_id'], $tags[1]['term_taxonomy_id'], $tags[3]['term_taxonomy_id'] ), + 'operator' => 'AND', + 'include_children' => false, + ) + ) + ) ); + + $this->assertEquals( array( $posts[0], $posts[1], $posts[2] ), $results1, 'Relation: OR; Operator: AND' ); + + $results2 = $q->query( array( + 'fields' => 'ids', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'orderby' => 'ID', + 'order' => 'ASC', + 'tax_query' => array( + 'relation' => 'AND', + array( + 'field' => 'term_taxonomy_id', + 'terms' => array( $cats[0]['term_taxonomy_id'], $tags[0]['term_taxonomy_id'] ), + 'operator' => 'IN', + 'include_children' => false, + ), + array( + 'field' => 'term_taxonomy_id', + 'terms' => array( $cats[3]['term_taxonomy_id'], $tags[3]['term_taxonomy_id'] ), + 'operator' => 'IN', + 'include_children' => false, + ) + ) + ) ); + + $this->assertEquals( array( $posts[0], $posts[3] ), $results2, 'Relation: AND; Operator: IN' ); + } + + /** + * @ticket 29738 + */ + public function test_populate_taxonomy_query_var_from_tax_query() { + register_taxonomy( 'foo', 'post' ); + $t = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $c = $this->factory->term->create( array( + 'taxonomy' => 'category', + ) ); + + // Create one post so ES has something to index + $this->factory->post->create(); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'tax_query' => array( + // Empty terms mean that this one should be skipped + array( + 'taxonomy' => 'bar', + 'terms' => array(), + ), + + // Category and post tags should be skipped + array( + 'taxonomy' => 'category', + 'terms' => array( $c ), + ), + + array( + 'taxonomy' => 'foo', + 'terms' => array( $t ), + ), + ), + ) ); + + $this->assertSame( 'foo', $q->get( 'taxonomy' ) ); + } + + public function test_populate_taxonomy_query_var_from_tax_query_taxonomy_already_set() { + register_taxonomy( 'foo', 'post' ); + register_taxonomy( 'foo1', 'post' ); + $t = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + + // Create one post so ES has something to index + $this->factory->post->create(); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'taxonomy' => 'bar', + 'tax_query' => array( + array( + 'taxonomy' => 'foo', + 'terms' => array( $t ), + ), + ), + ) ); + + $this->assertSame( 'bar', $q->get( 'taxonomy' ) ); + } + + public function test_populate_term_query_var_from_tax_query() { + register_taxonomy( 'foo', 'post' ); + $t = $this->factory->term->create( array( + 'taxonomy' => 'foo', + 'slug' => 'bar', + ) ); + + // Create one post so ES has something to index + $this->factory->post->create(); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'tax_query' => array( + array( + 'taxonomy' => 'foo', + 'terms' => array( 'bar' ), + 'field' => 'slug', + ), + ), + ) ); + + $this->assertSame( 'bar', $q->get( 'term' ) ); + } + + public function test_populate_term_id_query_var_from_tax_query() { + register_taxonomy( 'foo', 'post' ); + $t = $this->factory->term->create( array( + 'taxonomy' => 'foo', + 'slug' => 'bar', + ) ); + + // Create one post so ES has something to index + $this->factory->post->create(); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'tax_query' => array( + array( + 'taxonomy' => 'foo', + 'terms' => array( $t ), + 'field' => 'term_id', + ), + ), + ) ); + + $this->assertEquals( $t, $q->get( 'term_id' ) ); + } + + /** + * @ticket 29738 + */ + public function test_populate_cat_category_name_query_var_from_tax_query() { + register_taxonomy( 'foo', 'post' ); + $t = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $c = $this->factory->term->create( array( + 'taxonomy' => 'category', + 'slug' => 'bar', + ) ); + + // Create one post so ES has something to index + $this->factory->post->create(); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'tax_query' => array( + // Non-category should be skipped + array( + 'taxonomy' => 'foo', + 'terms' => array( $t ), + ), + + // Empty terms mean that this one should be skipped + array( + 'taxonomy' => 'category', + 'terms' => array(), + ), + + // Category and post tags should be skipped + array( + 'taxonomy' => 'category', + 'terms' => array( $c ), + ), + ), + ) ); + + $this->assertEquals( $c, $q->get( 'cat' ) ); + $this->assertEquals( 'bar', $q->get( 'category_name' ) ); + } + + /** + * @ticket 29738 + */ + public function test_populate_tag_id_query_var_from_tax_query() { + register_taxonomy( 'foo', 'post' ); + $t = $this->factory->term->create( array( + 'taxonomy' => 'foo', + ) ); + $tag = $this->factory->term->create( array( + 'taxonomy' => 'post_tag', + 'slug' => 'bar', + ) ); + + // Create one post so ES has something to index + $this->factory->post->create(); + es_wp_query_index_test_data(); + + $q = new ES_WP_Query( array( + 'tax_query' => array( + // Non-tag should be skipped + array( + 'taxonomy' => 'foo', + 'terms' => array( $t ), + ), + + // Empty terms mean that this one should be skipped + array( + 'taxonomy' => 'post_tag', + 'terms' => array(), + ), + + // Category and post tags should be skipped + array( + 'taxonomy' => 'post_tag', + 'terms' => array( $tag ), + ), + ), + ) ); + + $this->assertEquals( $tag, $q->get( 'tag_id' ) ); + } +}