Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply facets filters directly into the ES query and support and/or across different facet types #3045

Merged
merged 14 commits into from
Oct 17, 2022
Merged
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