Skip to content

Commit

Permalink
Merge pull request #3045 from 10up/feature/issue-2970
Browse files Browse the repository at this point in the history
Apply facets filters directly into the ES query and support and/or across different facet types
  • Loading branch information
felipeelia authored Oct 17, 2022
2 parents 2a8cfde + 42efcd6 commit 084ef7c
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 155 deletions.
97 changes: 94 additions & 3 deletions includes/classes/Feature/Facets/Facets.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public function setup() {
add_action( 'ep_feature_box_settings_facets', [ $this, 'settings' ], 10, 1 );
add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 );
add_action( 'pre_get_posts', [ $this, 'facet_query' ] );
add_filter( 'ep_post_filters', [ $this, 'apply_facets_filters' ], 10, 3 );
}

/**
Expand Down Expand Up @@ -151,6 +152,10 @@ public function set_agg_filters( $args, $query_args, $query ) {
return $args;
}

if ( 'any' === $this->get_match_type() ) {
add_filter( 'ep_post_filters', [ $this, 'remove_facets_filter' ], 11 );
}

/**
* Filter WP query arguments that will be used to build the aggregations filter.
*
Expand All @@ -170,6 +175,8 @@ public function set_agg_filters( $args, $query_args, $query ) {
$facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $query_args, $query );
add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 );

remove_filter( 'ep_post_filters', [ $this, 'remove_facets_filter' ], 11 );

$args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter'];

return $args;
Expand Down Expand Up @@ -277,12 +284,16 @@ public function is_facetable( $query ) {
* @since 2.5
*/
public function facet_query( $query ) {
$feature = Features::factory()->get_registered_feature( 'facets' );

if ( ! $feature->is_facetable( $query ) ) {
if ( ! $this->is_facetable( $query ) ) {
return;
}

// If any filter was selected, there is no reason to prepend the list with sticky posts.
$selected_filters = $this->get_selected();
if ( ! empty( array_filter( $selected_filters ) ) ) {
$query->set( 'ignore_sticky_posts', true );
}

/**
* Filter facet aggregations.
*
Expand Down Expand Up @@ -536,6 +547,86 @@ public function get_facetable_taxonomies() {

}

/**
* Add a new filter to the ES query with selected facets
*
* @since 4.4.0
* @param array $filters Current filters
* @param array $args WP Query args
* @param WP_Query $query WP Query object
* @return array
*/
public function apply_facets_filters( $filters, $args, $query ) {
if ( ! $this->is_facetable( $query ) ) {
return $filters;
}

/**
* Filter facet selection filters to be applied to the ES query
*
* @hook ep_facet_query_filters
* @since 4.4.0
* @param {array} $filters Current filters
* @param {array} $args WP Query args
* @param {WP_Query} $query WP Query object
* @return {array} New filters
*/
$facets_filters = apply_filters( 'ep_facet_query_filters', [], $args, $query );

if ( empty( $facets_filters ) ) {
return $filters;
}

$es_operator = ( 'any' === $this->get_match_type() ) ? 'should' : 'must';

$filters['facets'] = [
'bool' => [
$es_operator => $facets_filters,
],
];

return $filters;
}

/**
* Utilitary function to retrieve the match type selected by the user.
*
* @since 4.4.0
* @return string
*/
public function get_match_type() {
$settings = wp_parse_args(
$this->get_settings(),
array(
'match_type' => 'all',
)
);

/**
* Filter the match type of all facets. Can be 'all' or 'any'.
*
* @hook ep_facet_match_type
* @since 4.4.0
* @param {string} $match_type Current selection
* @return {string} New selection
*/
return apply_filters( 'ep_facet_match_type', $settings['match_type'] );
}

/**
* Given an array of filters, remove the facets filter.
*
* This is used when the user wants posts matching ANY criteria, so aggregations should not restrict their results.
*
* @since 4.4.0
* @param array $filters Filters to be applied to the ES query
* @return array
*/
public function remove_facets_filter( $filters ) {
unset( $filters['facets'] );
return $filters;
}

/**
* Figure out if Facet widget can display on page.
*
Expand Down
83 changes: 39 additions & 44 deletions includes/classes/Feature/Facets/Types/Meta/FacetType.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ class FacetType extends \ElasticPress\Feature\Facets\FacetType {
* Setup hooks and filters for feature
*/
public function setup() {
add_filter( 'ep_facet_agg_filters', [ $this, 'agg_filters' ], 10, 3 );
add_action( 'pre_get_posts', [ $this, 'facet_query' ] );
add_filter( 'ep_facet_query_filters', [ $this, 'add_query_filters' ] );
add_filter( 'ep_facet_wp_query_aggs_facet', [ $this, 'set_wp_query_aggs' ] );

add_action( 'ep_delete_post', [ $this, 'invalidate_meta_values_cache' ] );
Expand All @@ -53,48 +52,11 @@ public function setup() {
* @return array
*/
public function agg_filters( $query_args ) {
// Not a facetable query
if ( empty( $query_args['ep_facet'] ) ) {
return $query_args;
}

if ( ! class_exists( '\WP_Widget_Block' ) ) {
return $query_args;
}

// Without a meta_query, there is nothing to do here.
if ( empty( $query_args['meta_query'] ) || ! is_array( $query_args['meta_query'] ) ) {
return $query_args;
}

/**
* If the aggregations need to match ALL the criteria applied to the main query,
* all the filters applied to the main query should be applied to aggregations as well.
*/
$feature = Features::factory()->get_registered_feature( 'facets' );
$settings = wp_parse_args(
$feature->get_settings(),
array(
'match_type' => 'all',
)
_doing_it_wrong(
__METHOD__,
esc_html( 'Aggregation filters related to facet types are now managed by the main Facets class.' ),
'ElasticPress 4.4.0'
);
if ( 'all' === $settings['match_type'] ) {
return $query_args;
}

/**
* If we got to this point, let's remove from the aggregation filters all
* meta fields used in facets.
*/
$facets_meta_fields = $this->get_facets_meta_fields();

foreach ( $query_args['meta_query'] as $i => $meta_query_clause ) {
if ( is_array( $meta_query_clause )
&& ! empty( $meta_query_clause['key'] )
&& in_array( $meta_query_clause['key'], $facets_meta_fields, true ) ) {
unset( $query_args['meta_query'][ $i ] );
}
}

return $query_args;
}
Expand Down Expand Up @@ -186,11 +148,17 @@ public function set_wp_query_aggs( $facet_aggs ) {
}

/**
* Apply the facet selection to the main query.
* DEPRECATED. Apply the facet selection to the main query.
*
* @param WP_Query $query WP Query
*/
public function facet_query( $query ) {
_doing_it_wrong(
__METHOD__,
esc_html( 'Facet selections are now applied directly to the ES Query.' ),
'ElasticPress 4.4.0'
);

$feature = Features::factory()->get_registered_feature( 'facets' );

if ( ! $feature->is_facetable( $query ) ) {
Expand Down Expand Up @@ -230,6 +198,33 @@ public function facet_query( $query ) {
$query->set( 'ignore_sticky_posts', true );
}

/**
* Add selected filters to the Facet filter in the ES query
*
* @since 4.4.0
* @param array $filters Current Facet filters
* @return array
*/
public function add_query_filters( $filters ) {
$feature = Features::factory()->get_registered_feature( 'facets' );

$selected_filters = $feature->get_selected();
if ( empty( $selected_filters ) || empty( $selected_filters[ $this->get_filter_type() ] ) ) {
return $filters;
}

$meta_fields = $selected_filters[ $this->get_filter_type() ];
foreach ( $meta_fields as $meta_field => $values ) {
$filters[] = [
'terms' => [
'meta.' . $meta_field . '.raw' => array_keys( $values['terms'] ),
],
];
}

return $filters;
}

/**
* Get all fields selected in all Facet blocks
*
Expand Down
83 changes: 55 additions & 28 deletions includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ class FacetType extends \ElasticPress\Feature\Facets\FacetType {
*/
public function setup() {
add_action( 'widgets_init', [ $this, 'register_widgets' ] );
add_filter( 'ep_facet_agg_filters', [ $this, 'agg_filters' ] );
add_action( 'pre_get_posts', [ $this, 'facet_query' ] );
add_filter( 'ep_facet_query_filters', [ $this, 'add_query_filters' ] );
add_filter( 'ep_facet_wp_query_aggs_facet', [ $this, 'set_wp_query_aggs' ] );

$this->block = new Block();
Expand All @@ -42,33 +41,12 @@ public function setup() {
* @return array
*/
public function agg_filters( $query_args ) {
// Without taxonomies there is nothing to do here.
if ( empty( $query_args['tax_query'] ) ) {
return $query_args;
}

$feature = Features::factory()->get_registered_feature( 'facets' );
$settings = wp_parse_args(
$feature->get_settings(),
array(
'match_type' => 'all',
)
_doing_it_wrong(
__METHOD__,
esc_html( 'Aggregation filters related to facet types are now managed by the main Facets class.' ),
'ElasticPress 4.4.0'
);

if ( 'any' === $settings['match_type'] ) {
foreach ( $query_args['tax_query'] as $key => $taxonomy ) {
if ( is_array( $taxonomy ) ) {
unset( $query_args['tax_query'][ $key ] );
}
}
}

// @todo For some reason these are appearing in the query args, need to investigate
$unwanted_args = [ 'category_name', 'cat', 'tag', 'tag_id', 'taxonomy', 'term' ];
foreach ( $unwanted_args as $unwanted_arg ) {
unset( $query_args[ $unwanted_arg ] );
}

return $query_args;
}

Expand Down Expand Up @@ -138,13 +116,19 @@ public function get_facetable_taxonomies() {
}

/**
* We enable ElasticPress facet on all archive/search queries as well as non-static home pages. There is no way to know
* DEPRECATED. We enable ElasticPress facet on all archive/search queries as well as non-static home pages. There is no way to know
* when a facet widget is used before the main query is executed so we enable EP
* everywhere where a facet widget could be used.
*
* @param WP_Query $query WP Query
*/
public function facet_query( $query ) {
_doing_it_wrong(
__METHOD__,
esc_html( 'Facet selections are now applied directly to the ES Query.' ),
'ElasticPress 4.4.0'
);

$feature = Features::factory()->get_registered_feature( 'facets' );

if ( ! $feature->is_facetable( $query ) ) {
Expand Down Expand Up @@ -196,6 +180,49 @@ public function facet_query( $query ) {
$query->set( 'tax_query', $tax_query );
}

/**
* Add selected filters to the Facet filter in the ES query
*
* @since 4.4.0
* @param array $filters Current Facet filters
* @return array
*/
public function add_query_filters( $filters ) {
$feature = Features::factory()->get_registered_feature( 'facets' );

$taxonomies = $this->get_facetable_taxonomies();
if ( empty( $taxonomies ) ) {
return;
}

$selected_filters = $feature->get_selected();
if ( empty( $selected_filters ) || empty( $selected_filters[ $this->get_filter_type() ] ) ) {
return;
}

// Account for taxonomies that should be woocommerce attributes, if WC is enabled
$attribute_taxonomies = [];
if ( function_exists( 'wc_attribute_taxonomy_name' ) ) {
$all_attr_taxonomies = wc_get_attribute_taxonomies();

foreach ( $all_attr_taxonomies as $attr_taxonomy ) {
$attribute_taxonomies[ $attr_taxonomy->attribute_name ] = wc_attribute_taxonomy_name( $attr_taxonomy->attribute_name );
}
}

foreach ( $selected_filters['taxonomies'] as $taxonomy => $filter ) {
$taxonomy_slug = $attribute_taxonomies[ $taxonomy ] ?? $taxonomy;

$filters[] = [
'terms' => [
'terms.' . $taxonomy_slug . '.slug' => array_keys( $filter['terms'] ),
],
];
}

return $filters;
}

/**
* Add taxonomies to facets aggs
*
Expand Down
18 changes: 18 additions & 0 deletions tests/cypress/integration/features/facets.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,24 @@ describe('Facets Feature', () => {
cy.get('@secondBlock').contains('.term', 'Meta Value (2) - 20').click();
cy.url().should('not.include', 'ep_meta_filter_meta_field_2=Meta+Value+%282%29+-+20');
cy.url().should('include', 'ep_meta_filter_meta_field_1=Meta+Value+%281%29+-+20');
cy.get('@secondBlock')
.contains('a[aria-disabled="true"]', 'Meta Value (2) - 19')
.should('exist');

/**
* When Match Type is "any", all options need to be clickable
*/
cy.visitAdminPage('admin.php?page=elasticpress');
cy.get('.ep-feature-facets .settings-button').click();
cy.get('input[name="settings[match_type]"][value="any"]').check();
cy.get('.ep-feature-facets .button-primary').click();

cy.visit('/');
cy.get('@secondBlock').contains('.term', 'Meta Value (2) - 20').click();
cy.get('@secondBlock').contains('.term', 'Meta Value (2) - 1').click();
cy.get('.wp-block-elasticpress-facet a[aria-disabled="true"]').should('not.exist');
cy.contains('.site-content article h2', 'Facet By Meta Post 20').should('exist');
cy.contains('.site-content article h2', 'Facet By Meta Post 1').should('exist');
});
});
});
Loading

0 comments on commit 084ef7c

Please sign in to comment.