From ef1eb9c56243a6f1793309443c3ed1d4ddd20b3d Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Mon, 1 Aug 2022 18:00:27 -0300 Subject: [PATCH 01/19] Move taxonomy facets to a separate class --- includes/classes/Feature/Facets/Facets.php | 271 +++------------ .../Facets/{ => Types/Taxonomy}/Block.php | 6 +- .../Facets/Types/Taxonomy/FacetType.php | 323 ++++++++++++++++++ .../Facets/{ => Types/Taxonomy}/Renderer.php | 12 +- .../Facets/{ => Types/Taxonomy}/Widget.php | 4 +- 5 files changed, 373 insertions(+), 243 deletions(-) rename includes/classes/Feature/Facets/{ => Types/Taxonomy}/Block.php (96%) create mode 100644 includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php rename includes/classes/Feature/Facets/{ => Types/Taxonomy}/Renderer.php (97%) rename includes/classes/Feature/Facets/{ => Types/Taxonomy}/Widget.php (97%) diff --git a/includes/classes/Feature/Facets/Facets.php b/includes/classes/Feature/Facets/Facets.php index c6717d0fbe..c085b917f7 100644 --- a/includes/classes/Feature/Facets/Facets.php +++ b/includes/classes/Feature/Facets/Facets.php @@ -22,14 +22,13 @@ * Facets feature class */ class Facets extends Feature { - /** - * Block instance. + * Facet types (taxonomy, meta fields, etc.) * - * @since 4.2.0 - * @var Block + * @since 4.3.0 + * @var array */ - public $block; + public $types = []; /** * Initialize feature setting it's config @@ -51,6 +50,14 @@ public function __construct() { 'match_type' => 'all', ]; + $types = [ + 'taxonomy' => __NAMESPACE__ . '\Types\Taxonomy\FacetType', + ]; + + foreach ( $types as $type => $class ) { + $this->types[ $type ] = new $class(); + } + parent::__construct(); } @@ -67,16 +74,14 @@ public function setup() { return; } - add_action( 'widgets_init', [ $this, 'register_widgets' ] ); + foreach ( $this->types as $type => $class ) { + $this->types[ $type ]->setup(); + } + add_action( 'ep_valid_response', [ $this, 'get_aggs' ], 10, 4 ); - add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - add_action( 'pre_get_posts', [ $this, 'facet_query' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'front_scripts' ] ); add_action( 'ep_feature_box_settings_facets', [ $this, 'settings' ], 10, 1 ); - - $this->block = new Block(); - $this->block->setup(); } /** @@ -110,54 +115,15 @@ public function output_feature_box_settings() { * @param array $args ES arguments * @param array $query_args Query arguments * @param WP_Query $query WP Query instance - * @since 2.5 + * @since 2.5, deprecated in 4.3.0 * @return array */ public function set_agg_filters( $args, $query_args, $query ) { - if ( empty( $query_args['ep_facet'] ) ) { - return $args; - } + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->set_agg_filters()" ); - // @todo For some reason these are appearing in the query args, need to investigate - unset( $query_args['category_name'] ); - unset( $query_args['cat'] ); - unset( $query_args['tag'] ); - unset( $query_args['tag_id'] ); - unset( $query_args['taxonomy'] ); - unset( $query_args['term'] ); - - $facet_query_args = $query_args; - - $settings = $this->get_settings(); + return $this->types['taxonomy']->set_agg_filters( $args, $query_args, $query ); - $settings = wp_parse_args( - $settings, - array( - 'match_type' => 'all', - ) - ); - - if ( ! empty( $facet_query_args['tax_query'] ) ) { - remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - - foreach ( $facet_query_args['tax_query'] as $key => $taxonomy ) { - if ( is_array( $taxonomy ) ) { - if ( 'any' === $settings['match_type'] ) { - unset( $facet_query_args['tax_query'][ $key ] ); - } - } - } - - $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_query_args, $query ); - - $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; - - add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - } - - return $args; } - /** * Output scripts for widget admin * @@ -257,105 +223,12 @@ public function is_facetable( $query ) { * everywhere where a facet widget could be used. * * @param WP_Query $query WP Query - * @since 2.5 + * @since 2.5, deprecated in 4.3.0 */ public function facet_query( $query ) { - if ( ! $this->is_facetable( $query ) ) { - return; - } - - $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); - - /** - * Filter taxonomies made available for faceting - * - * @hook ep_facet_include_taxonomies - * @param {array} $taxonomies Taxonomies - * @return {array} New taxonomies - */ - $taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); - - if ( empty( $taxonomies ) ) { - return; - } - - $query->set( 'ep_integrate', true ); - $query->set( 'ep_facet', true ); - - $facets = []; - - /** - * Retrieve aggregations based on a custom field. This field must exist on the mapping. - * Values available out-of-the-box are: - * - slug (default) - * - term_id - * - name - * - parent - * - term_taxonomy_id - * - term_order - * - facet (retrieves a JSON representation of the term object) - * - * @since 3.6.0 - * @hook ep_facet_use_field - * @param {string} $field The term field to use - * @return {string} The chosen term field - */ - $facet_field = apply_filters( 'ep_facet_use_field', 'slug' ); - - foreach ( $taxonomies as $slug => $taxonomy ) { - $facets[ $slug ] = array( - 'terms' => array( - 'size' => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ), - 'field' => 'terms.' . $slug . '.' . $facet_field, - ), - ); - } - - $aggs = array( - 'name' => 'terms', - 'use-filter' => true, - 'aggs' => $facets, - ); - - $query->set( 'aggs', $aggs ); - - $selected_filters = $this->get_selected(); - - $settings = $this->get_settings(); - - $settings = wp_parse_args( - $settings, - array( - 'match_type' => 'all', - ) - ); - - $tax_query = $query->get( 'tax_query', [] ); + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->facet_query()" ); - // 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 ) { - $tax_query[] = [ - 'taxonomy' => isset( $attribute_taxonomies[ $taxonomy ] ) ? $attribute_taxonomies[ $taxonomy ] : $taxonomy, - 'field' => 'slug', - 'terms' => array_keys( $filter['terms'] ), - 'operator' => ( 'any' === $settings['match_type'] ) ? 'or' : 'and', - ]; - } - - if ( ! empty( $selected_filters['taxonomies'] ) && 'any' === $settings['match_type'] ) { - $tax_query['relation'] = 'or'; - } - - $query->set( 'tax_query', $tax_query ); + return $this->types['taxonomy']->facet_query( $query ); } /** @@ -400,92 +273,35 @@ public function get_aggs( $response, $query, $query_args, $query_object ) { /** * Get currently selected facets from query args * - * @since 2.5 + * @since 2.5, deprecated in 4.3.0 * @return array */ public function get_selected() { - $filters = array( - 'taxonomies' => [], - ); - - $allowed_args = $this->get_allowed_query_args(); - $filter_name = $this->get_filter_name(); - - foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification - if ( 0 === strpos( $key, $filter_name ) ) { - $taxonomy = str_replace( $filter_name, '', $key ); - - $filters['taxonomies'][ $taxonomy ] = array( - 'terms' => array_fill_keys( array_map( 'trim', explode( ',', trim( $value, ',' ) ) ), true ), - ); - } - - if ( in_array( $key, $allowed_args, true ) ) { - $filters[ $key ] = $value; - } - } + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->get_selected()" ); - return $filters; + return $this->types['taxonomy']->get_selected(); } /** * Build query url * - * @since 2.5 + * @since 2.5, deprecated in 4.3.0 * @param array $filters Facet filters * @return string */ public function build_query_url( $filters ) { - $query_param = array(); + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->build_query_url()" ); - if ( ! empty( $filters['taxonomies'] ) ) { - $tax_filters = $filters['taxonomies']; - - foreach ( $tax_filters as $taxonomy => $filter ) { - if ( ! empty( $filter['terms'] ) ) { - $query_param[ $this->get_filter_name() . $taxonomy ] = implode( ',', array_keys( $filter['terms'] ) ); - } - } - } - - $allowed_args = $this->get_allowed_query_args(); - - if ( ! empty( $filters ) ) { - foreach ( $filters as $filter => $value ) { - if ( ! empty( $value ) && in_array( $filter, $allowed_args, true ) ) { - $query_param[ $filter ] = $value; - } - } - } - - $query_string = http_build_query( $query_param ); - - /** - * Filter facet query string - * - * @hook ep_facet_query_string - * @param {string} $query_string Current query string - * @param {array} $query_param Query parameters - * @return {string} New query string - */ - $query_string = apply_filters( 'ep_facet_query_string', $query_string, $query_param ); - - $url = $_SERVER['REQUEST_URI']; - $pagination = strpos( $url, '/page' ); - if ( false !== $pagination ) { - $url = substr( $url, 0, $pagination ); - } - - return strtok( trailingslashit( $url ), '?' ) . ( ( ! empty( $query_string ) ) ? '?' . $query_string : '' ); + return $this->types['taxonomy']->build_query_url( $filters ); } /** * Register facet widget(s) * - * @since 2.5 + * @since 2.5, deprecated in 4.3.0 */ public function register_widgets() { - register_widget( __NAMESPACE__ . '\Widget' ); + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types[ \$type ]->register_widgets()" ); } /** @@ -530,33 +346,22 @@ public function get_allowed_query_args() { * @return string The filter name. */ protected function get_filter_name() { - /** - * Filter the facet filter name that's added to the URL - * - * @hook ep_facet_filter_name - * @since 4.0.0 - * @param {string} Facet filter name - * @return {string} New facet filter name - */ - return apply_filters( 'ep_facet_filter_name', 'ep_filter_' ); + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->get_filter_name()" ); + + return $this->types['taxonomy']->get_filter_name(); } /** * Get all taxonomies that could be selected for a facet. * - * @since 4.2.0 + * @since 4.2.0, deprecated in 4.3.0 * @return array */ public function get_facetable_taxonomies() { - $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); - /** - * Filter taxonomies made available for faceting - * - * @hook ep_facet_include_taxonomies - * @param {array} $taxonomies Taxonomies - * @return {array} New taxonomies - */ - return apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); + _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->get_facetable_taxonomies()" ); + + return $this->types['taxonomy']->get_filter_name(); + } /** diff --git a/includes/classes/Feature/Facets/Block.php b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php similarity index 96% rename from includes/classes/Feature/Facets/Block.php rename to includes/classes/Feature/Facets/Types/Taxonomy/Block.php index bd529f3d32..e2160cc65b 100644 --- a/includes/classes/Feature/Facets/Block.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php @@ -6,7 +6,7 @@ * @package elasticpress */ -namespace ElasticPress\Feature\Facets; +namespace ElasticPress\Feature\Facets\Types\Taxonomy; use ElasticPress\Features; @@ -83,7 +83,7 @@ public function check_facets_taxonomies_rest_permission() { * @return array */ public function get_rest_facetable_taxonomies() { - $taxonomies_raw = Features::factory()->get_registered_feature( 'facets' )->get_facetable_taxonomies(); + $taxonomies_raw = Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->get_facetable_taxonomies(); $taxonomies = []; foreach ( $taxonomies_raw as $slug => $taxonomy ) { @@ -203,7 +203,7 @@ protected function parse_attributes( $attributes ) { ] ); if ( empty( $attributes['facet'] ) ) { - $taxonomies = Features::factory()->get_registered_feature( 'facets' )->get_facetable_taxonomies(); + $taxonomies = Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->get_facetable_taxonomies(); if ( ! empty( $taxonomies ) ) { $attributes['facet'] = key( $taxonomies ); } diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php new file mode 100644 index 0000000000..bebadd3f88 --- /dev/null +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -0,0 +1,323 @@ +block = new Block(); + $this->block->setup(); + } + + /** + * If we are doing or matches, we need to remove filters from aggs + * + * @param array $args ES arguments + * @param array $query_args Query arguments + * @param WP_Query $query WP Query instance + * @return array + */ + public function set_agg_filters( $args, $query_args, $query ) { + if ( empty( $query_args['ep_facet'] ) ) { + return $args; + } + + // @todo For some reason these are appearing in the query args, need to investigate + unset( $query_args['category_name'] ); + unset( $query_args['cat'] ); + unset( $query_args['tag'] ); + unset( $query_args['tag_id'] ); + unset( $query_args['taxonomy'] ); + unset( $query_args['term'] ); + + $facet_query_args = $query_args; + + $feature = Features::factory()->get_registered_feature( 'facets' ); + $settings = $feature->get_settings(); + + $settings = wp_parse_args( + $settings, + array( + 'match_type' => 'all', + ) + ); + + if ( ! empty( $facet_query_args['tax_query'] ) ) { + remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + + foreach ( $facet_query_args['tax_query'] as $key => $taxonomy ) { + if ( is_array( $taxonomy ) ) { + if ( 'any' === $settings['match_type'] ) { + unset( $facet_query_args['tax_query'][ $key ] ); + } + } + } + + $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_query_args, $query ); + + $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; + + add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + } + + return $args; + } + + /** + * Get currently selected facets from query args + * + * @return array + */ + public function get_selected() { + $feature = Features::factory()->get_registered_feature( 'facets' ); + + $filters = array( + 'taxonomies' => [], + ); + + $allowed_args = $feature->get_allowed_query_args(); + $filter_name = $this->get_filter_name(); + + foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification + if ( 0 === strpos( $key, $filter_name ) ) { + $taxonomy = str_replace( $filter_name, '', $key ); + + $filters['taxonomies'][ $taxonomy ] = array( + 'terms' => array_fill_keys( array_map( 'trim', explode( ',', trim( $value, ',' ) ) ), true ), + ); + } + + if ( in_array( $key, $allowed_args, true ) ) { + $filters[ $key ] = $value; + } + } + + return $filters; + } + + /** + * Build query url + * + * @param array $filters Facet filters + * @return string + */ + public function build_query_url( $filters ) { + $query_param = array(); + + if ( ! empty( $filters['taxonomies'] ) ) { + $tax_filters = $filters['taxonomies']; + + foreach ( $tax_filters as $taxonomy => $filter ) { + if ( ! empty( $filter['terms'] ) ) { + $query_param[ $this->get_filter_name() . $taxonomy ] = implode( ',', array_keys( $filter['terms'] ) ); + } + } + } + + $feature = Features::factory()->get_registered_feature( 'facets' ); + $allowed_args = $feature->get_allowed_query_args(); + + if ( ! empty( $filters ) ) { + foreach ( $filters as $filter => $value ) { + if ( ! empty( $value ) && in_array( $filter, $allowed_args, true ) ) { + $query_param[ $filter ] = $value; + } + } + } + + $query_string = http_build_query( $query_param ); + + /** + * Filter facet query string + * + * @hook ep_facet_query_string + * @param {string} $query_string Current query string + * @param {array} $query_param Query parameters + * @return {string} New query string + */ + $query_string = apply_filters( 'ep_facet_query_string', $query_string, $query_param ); + + $url = $_SERVER['REQUEST_URI']; + $pagination = strpos( $url, '/page' ); + if ( false !== $pagination ) { + $url = substr( $url, 0, $pagination ); + } + + return strtok( trailingslashit( $url ), '?' ) . ( ( ! empty( $query_string ) ) ? '?' . $query_string : '' ); + } + + /** + * Register facet widget(s) + */ + public function register_widgets() { + register_widget( __NAMESPACE__ . '\Widget' ); + } + + /** + * Get the facet filter name. + * + * @return string The filter name. + */ + protected function get_filter_name() { + /** + * Filter the facet filter name that's added to the URL + * + * @hook ep_facet_filter_name + * @since 4.0.0 + * @param {string} Facet filter name + * @return {string} New facet filter name + */ + return apply_filters( 'ep_facet_filter_name', 'ep_filter_' ); + } + + /** + * Get all taxonomies that could be selected for a facet. + * + * @return array + */ + public function get_facetable_taxonomies() { + $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); + /** + * Filter taxonomies made available for faceting + * + * @hook ep_facet_include_taxonomies + * @param {array} $taxonomies Taxonomies + * @return {array} New taxonomies + */ + return apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); + } + + /** + * 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 ) { + $feature = Features::factory()->get_registered_feature( 'facets' ); + + if ( ! $feature->is_facetable( $query ) ) { + return; + } + + $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); + + /** + * Filter taxonomies made available for faceting + * + * @hook ep_facet_include_taxonomies + * @param {array} $taxonomies Taxonomies + * @return {array} New taxonomies + */ + $taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); + + if ( empty( $taxonomies ) ) { + return; + } + + $query->set( 'ep_integrate', true ); + $query->set( 'ep_facet', true ); + + $facets = []; + + /** + * Retrieve aggregations based on a custom field. This field must exist on the mapping. + * Values available out-of-the-box are: + * - slug (default) + * - term_id + * - name + * - parent + * - term_taxonomy_id + * - term_order + * - facet (retrieves a JSON representation of the term object) + * + * @since 3.6.0 + * @hook ep_facet_use_field + * @param {string} $field The term field to use + * @return {string} The chosen term field + */ + $facet_field = apply_filters( 'ep_facet_use_field', 'slug' ); + + foreach ( $taxonomies as $slug => $taxonomy ) { + $facets[ $slug ] = array( + 'terms' => array( + 'size' => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ), + 'field' => 'terms.' . $slug . '.' . $facet_field, + ), + ); + } + + $aggs = array( + 'name' => 'terms', + 'use-filter' => true, + 'aggs' => $facets, + ); + + $query->set( 'aggs', $aggs ); + + $selected_filters = $this->get_selected(); + + $settings = $feature->get_settings(); + + $settings = wp_parse_args( + $settings, + array( + 'match_type' => 'all', + ) + ); + + $tax_query = $query->get( 'tax_query', [] ); + + // 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 ) { + $tax_query[] = [ + 'taxonomy' => isset( $attribute_taxonomies[ $taxonomy ] ) ? $attribute_taxonomies[ $taxonomy ] : $taxonomy, + 'field' => 'slug', + 'terms' => array_keys( $filter['terms'] ), + 'operator' => ( 'any' === $settings['match_type'] ) ? 'or' : 'and', + ]; + } + + if ( ! empty( $selected_filters['taxonomies'] ) && 'any' === $settings['match_type'] ) { + $tax_query['relation'] = 'or'; + } + + $query->set( 'tax_query', $tax_query ); + } +} diff --git a/includes/classes/Feature/Facets/Renderer.php b/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php similarity index 97% rename from includes/classes/Feature/Facets/Renderer.php rename to includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php index 6032a82ad8..205b86fc26 100644 --- a/includes/classes/Feature/Facets/Renderer.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php @@ -6,7 +6,7 @@ * @package elasticpress */ -namespace ElasticPress\Feature\Facets; +namespace ElasticPress\Feature\Facets\Types\Taxonomy; use ElasticPress\Features as Features; use ElasticPress\Utils as Utils; @@ -75,7 +75,9 @@ public function render( $args, $instance ) { } } - $selected_filters = $feature->get_selected(); + $facet_type = $feature->types['taxonomy']; + + $selected_filters = $facet_type->get_selected(); $match_type = ( ! empty( $instance['match_type'] ) ) ? $instance['match_type'] : 'all'; @@ -193,7 +195,7 @@ public function render( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_facet_term_html( $term, - $feature->build_query_url( $new_filters ), + $facet_type->build_query_url( $new_filters ), true ); // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped @@ -256,7 +258,7 @@ public function render( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_facet_term_html( $term, - $feature->build_query_url( $new_filters ), + $facet_type->build_query_url( $new_filters ), $selected ); // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped @@ -285,7 +287,7 @@ public function render( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_facet_term_html( $term, - $feature->build_query_url( $new_filters ) + $facet_type->build_query_url( $new_filters ) ); // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped endforeach; diff --git a/includes/classes/Feature/Facets/Widget.php b/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php similarity index 97% rename from includes/classes/Feature/Facets/Widget.php rename to includes/classes/Feature/Facets/Types/Taxonomy/Widget.php index ca53a43ee3..1c27ca49d3 100644 --- a/includes/classes/Feature/Facets/Widget.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php @@ -5,7 +5,7 @@ * @package elasticpress */ -namespace ElasticPress\Feature\Facets; +namespace ElasticPress\Feature\Facets\Types\Taxonomy; use \WP_Widget as WP_Widget; use ElasticPress\Features as Features; @@ -101,7 +101,7 @@ public function form( $instance ) { $orderby = ( ! empty( $instance['orderby'] ) ) ? $instance['orderby'] : ''; $order = ( ! empty( $instance['order'] ) ) ? $instance['order'] : ''; - $taxonomies = $feature->get_facetable_taxonomies(); + $taxonomies = $feature->types['taxonomy']->get_facetable_taxonomies(); $orderby_options = [ 'count' => __( 'Count', 'elasticpress' ), From 5d2c9e0517e11df2ab5e03eaa476f188a276fc61 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 3 Aug 2022 10:15:14 -0300 Subject: [PATCH 02/19] Move tests and mark as deprecated --- tests/php/features/TestFacet.php | 101 +--------------- tests/php/features/TestFacetTypeTaxonomy.php | 120 +++++++++++++++++++ 2 files changed, 124 insertions(+), 97 deletions(-) create mode 100644 tests/php/features/TestFacetTypeTaxonomy.php diff --git a/tests/php/features/TestFacet.php b/tests/php/features/TestFacet.php index 93dba823f8..a322abe7ee 100644 --- a/tests/php/features/TestFacet.php +++ b/tests/php/features/TestFacet.php @@ -13,66 +13,15 @@ * Facet test class */ class TestFacets extends BaseTestCase { - - /** - * Setup each test. - * - * @since 3.6.0 - */ - public function setUp() { - parent::setUp(); - } - - /** - * Clean up after each test. - * - * @since 3.6.0 - */ - public function tearDown() { - parent::tearDown(); - } - - /** * Test the `get_selected` method * * @since 3.6.0 * @group facets - * + * @expectedDeprecated ElasticPress\Feature\Facets\Facets::get_selected */ public function testGetSelected() { - $facet_feature = Features::factory()->get_registered_feature( 'facets' ); - - parse_str( 'ep_filter_taxonomy=dolor', $_GET ); - $selected = $facet_feature->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); - - parse_str( 'ep_filter_taxonomy=dolor,sit', $_GET ); - $selected = $facet_feature->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true, 'sit' => true ], 'taxonomy', $selected ); - - parse_str( 'ep_filter_taxonomy=dolor,sit&ep_filter_othertax=amet', $_GET ); - $selected = $facet_feature->get_selected(); - - $this->assertIsArray( $selected ); - $this->assertIsArray( $selected['taxonomies'] ); - $this->assertCount( 2, $selected['taxonomies'] ); - $this->assertArrayHasKey( 'taxonomy', $selected['taxonomies'] ); - $this->assertArrayHasKey( 'othertax', $selected['taxonomies'] ); - $this->assertSame( [ 'dolor' => true, 'sit' => true ], $selected['taxonomies']['taxonomy']['terms'] ); - $this->assertSame( [ 'amet' => true ], $selected['taxonomies']['othertax']['terms'] ); - - parse_str( 's=searchterm&ep_filter_taxonomy=dolor', $_GET ); - $selected = $facet_feature->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); - $this->assertArrayHasKey( 's', $selected ); - $this->assertSame( 'searchterm', $selected['s'] ); - - parse_str( 'post_type=posttype&ep_filter_taxonomy=dolor', $_GET ); - $selected = $facet_feature->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); - $this->assertArrayHasKey( 'post_type', $selected ); - $this->assertSame( 'posttype', $selected['post_type'] ); + Features::factory()->get_registered_feature( 'facets' )->get_selected(); } /** @@ -80,11 +29,9 @@ public function testGetSelected() { * * @since 3.6.0 * @group facets - * + * @expectedDeprecated ElasticPress\Feature\Facets\Facets::build_query_url */ public function testBuildQueryUrl() { - $facet_feature = Features::factory()->get_registered_feature( 'facets' ); - $filters = [ 'taxonomies' => [ 'category' => [ @@ -94,46 +41,6 @@ public function testBuildQueryUrl() { ] ] ]; - - $this->assertEquals( '/?ep_filter_category=augue', $facet_feature->build_query_url( $filters ) ); - - $filters['s'] = 'dolor'; - $this->assertEquals( '/?ep_filter_category=augue&s=dolor', $facet_feature->build_query_url( $filters ) ); - - unset( $filters['s'] ); - $filters = [ - 'taxonomies' => [ - 'category' => [ - 'terms' => [ - 'augue' => 1, - 'consectetur' => 1 - ] - ] - ] - ]; - - $this->assertEquals( '/?ep_filter_category=augue%2Cconsectetur', $facet_feature->build_query_url( $filters ) ); - - $_SERVER['REQUEST_URI'] = 'test/page/1'; - - $filters['s'] = 'dolor'; - $this->assertEquals( 'test/?ep_filter_category=augue%2Cconsectetur&s=dolor', $facet_feature->build_query_url( $filters ) ); - } - - /** - * Utilitary function for the testGetSelected test. - * - * Private as it is super specific and not likely to be extended. - * - * @param array $terms Array of terms as they should be in the $selected array. - * @param string $taxonomy Taxonomy slug - * @param array $selected Array of selected terms. - */ - private function assertSelectedTax( $terms, $taxonomy, $selected ) { - $this->assertIsArray( $selected ); - $this->assertIsArray( $selected['taxonomies'] ); - $this->assertCount( 1, $selected['taxonomies'] ); - $this->assertArrayHasKey( $taxonomy, $selected['taxonomies'] ); - $this->assertSame( $terms, $selected['taxonomies'][ $taxonomy ]['terms'] ); + Features::factory()->get_registered_feature( 'facets' )->build_query_url( $filters ); } } diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php new file mode 100644 index 0000000000..19f3148a70 --- /dev/null +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -0,0 +1,120 @@ +get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + parse_str( 'ep_filter_taxonomy=dolor', $_GET ); + $selected = $facet_type->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); + + parse_str( 'ep_filter_taxonomy=dolor,sit', $_GET ); + $selected = $facet_type->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true, 'sit' => true ], 'taxonomy', $selected ); + + parse_str( 'ep_filter_taxonomy=dolor,sit&ep_filter_othertax=amet', $_GET ); + $selected = $facet_type->get_selected(); + + $this->assertIsArray( $selected ); + $this->assertIsArray( $selected['taxonomies'] ); + $this->assertCount( 2, $selected['taxonomies'] ); + $this->assertArrayHasKey( 'taxonomy', $selected['taxonomies'] ); + $this->assertArrayHasKey( 'othertax', $selected['taxonomies'] ); + $this->assertSame( [ 'dolor' => true, 'sit' => true ], $selected['taxonomies']['taxonomy']['terms'] ); + $this->assertSame( [ 'amet' => true ], $selected['taxonomies']['othertax']['terms'] ); + + parse_str( 's=searchterm&ep_filter_taxonomy=dolor', $_GET ); + $selected = $facet_type->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); + $this->assertArrayHasKey( 's', $selected ); + $this->assertSame( 'searchterm', $selected['s'] ); + + parse_str( 'post_type=posttype&ep_filter_taxonomy=dolor', $_GET ); + $selected = $facet_type->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); + $this->assertArrayHasKey( 'post_type', $selected ); + $this->assertSame( 'posttype', $selected['post_type'] ); + } + + /** + * Test build query URL + * + * @since 4.3.0 + * @group facets + */ + public function testBuildQueryUrl() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $filters = [ + 'taxonomies' => [ + 'category' => [ + 'terms' => [ + 'augue' => 1 + ] + ] + ] + ]; + + $this->assertEquals( '/?ep_filter_category=augue', $facet_type->build_query_url( $filters ) ); + + $filters['s'] = 'dolor'; + $this->assertEquals( '/?ep_filter_category=augue&s=dolor', $facet_type->build_query_url( $filters ) ); + + unset( $filters['s'] ); + $filters = [ + 'taxonomies' => [ + 'category' => [ + 'terms' => [ + 'augue' => 1, + 'consectetur' => 1 + ] + ] + ] + ]; + + $this->assertEquals( '/?ep_filter_category=augue%2Cconsectetur', $facet_type->build_query_url( $filters ) ); + + $_SERVER['REQUEST_URI'] = 'test/page/1'; + + $filters['s'] = 'dolor'; + $this->assertEquals( 'test/?ep_filter_category=augue%2Cconsectetur&s=dolor', $facet_type->build_query_url( $filters ) ); + } + + /** + * Utilitary function for the testGetSelected test. + * + * Private as it is super specific and not likely to be extended. + * + * @param array $terms Array of terms as they should be in the $selected array. + * @param string $taxonomy Taxonomy slug + * @param array $selected Array of selected terms. + */ + private function assertSelectedTax( $terms, $taxonomy, $selected ) { + $this->assertIsArray( $selected ); + $this->assertIsArray( $selected['taxonomies'] ); + $this->assertCount( 1, $selected['taxonomies'] ); + $this->assertArrayHasKey( $taxonomy, $selected['taxonomies'] ); + $this->assertSame( $terms, $selected['taxonomies'][ $taxonomy ]['terms'] ); + } +} From af83a5b43453cfc8cfa67dc50c6f49bdf6ccf090 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 3 Aug 2022 10:31:23 -0300 Subject: [PATCH 03/19] Add tests for the ep_facet_query_string and ep_facet_filter_name filters --- tests/php/features/TestFacetTypeTaxonomy.php | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 19f3148a70..5d22f89a07 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -99,6 +99,29 @@ public function testBuildQueryUrl() { $filters['s'] = 'dolor'; $this->assertEquals( 'test/?ep_filter_category=augue%2Cconsectetur&s=dolor', $facet_type->build_query_url( $filters ) ); + + /** + * Test the `ep_facet_query_string` filter. + */ + $change_facet_query_string = function ( $query_string, $query_params ) { + $this->assertIsArray( $query_params ); + $query_string .= '&foobar'; + return $query_string; + }; + add_filter( 'ep_facet_query_string', $change_facet_query_string, 10, 2 ); + $this->assertStringEndsWith( '&foobar', $facet_type->build_query_url( $filters ) ); + remove_filter( 'ep_facet_query_string', $change_facet_query_string, 10, 2 ); + + /** + * (Indirectly) test the `ep_facet_filter_name` filter + */ + $change_ep_facet_filter_name = function( $original_name ) { + $this->assertEquals( 'ep_filter_', $original_name ); + return 'ep_custom_filter_'; + }; + add_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); + $this->assertEquals( 'test/?ep_custom_filter_category=augue%2Cconsectetur&s=dolor', $facet_type->build_query_url( $filters ) ); + remove_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); } /** From d623bcd9015c7e10af37b7c7431486cda5164e4e Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 3 Aug 2022 10:33:43 -0300 Subject: [PATCH 04/19] Only unset args if they are set --- .../Feature/Facets/Types/Taxonomy/FacetType.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index bebadd3f88..9c8375bb4b 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -49,12 +49,10 @@ public function set_agg_filters( $args, $query_args, $query ) { } // @todo For some reason these are appearing in the query args, need to investigate - unset( $query_args['category_name'] ); - unset( $query_args['cat'] ); - unset( $query_args['tag'] ); - unset( $query_args['tag_id'] ); - unset( $query_args['taxonomy'] ); - unset( $query_args['term'] ); + $unwanted_args = [ 'category_name', 'cat', 'tag', 'tag_id', 'taxonomy', 'term' ]; + foreach ( $unwanted_args as $unwanted_arg ) { + unset( $query_args[ $unwanted_arg ] ); + } $facet_query_args = $query_args; From f1f62851fda8efaec8c6b38deafdef3849d7eee8 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 3 Aug 2022 11:40:32 -0300 Subject: [PATCH 05/19] set_agg_filters refactor + some tests --- .../Facets/Types/Taxonomy/FacetType.php | 38 ++++++++-------- tests/php/features/TestFacetTypeTaxonomy.php | 44 +++++++++++++++++++ 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index 9c8375bb4b..08dc357340 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -44,45 +44,45 @@ public function setup() { * @return array */ public function set_agg_filters( $args, $query_args, $query ) { + // Not a facetable query if ( empty( $query_args['ep_facet'] ) ) { return $args; } - // @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 ] ); + // Without taxonomies there is nothing to do here. + if ( empty( $query_args['tax_query'] ) ) { + return $args; } - $facet_query_args = $query_args; - $feature = Features::factory()->get_registered_feature( 'facets' ); - $settings = $feature->get_settings(); - $settings = wp_parse_args( - $settings, + $feature->get_settings(), array( 'match_type' => 'all', ) ); - if ( ! empty( $facet_query_args['tax_query'] ) ) { - remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + $facet_query_args = $query_args; + if ( 'any' === $settings['match_type'] ) { foreach ( $facet_query_args['tax_query'] as $key => $taxonomy ) { if ( is_array( $taxonomy ) ) { - if ( 'any' === $settings['match_type'] ) { - unset( $facet_query_args['tax_query'][ $key ] ); - } + unset( $facet_query_args['tax_query'][ $key ] ); } } + } - $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_query_args, $query ); + // @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( $facet_query_args[ $unwanted_arg ] ); + } - $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; + remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_query_args, $query ); + add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - } + $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; return $args; } @@ -102,6 +102,8 @@ public function get_selected() { $allowed_args = $feature->get_allowed_query_args(); $filter_name = $this->get_filter_name(); + $escaped_get_keys = array_map( 'sanitize_key', array_keys( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification + $escaped_get_keys = array_map( 'sanitize_key', array_keys( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification if ( 0 === strpos( $key, $filter_name ) ) { $taxonomy = str_replace( $filter_name, '', $key ); diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 5d22f89a07..9d6d48843b 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -124,6 +124,50 @@ public function testBuildQueryUrl() { remove_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); } + /** + * Test set_agg_filters + * + * @since 4.3.0 + * @group facets + */ + public function testSetAggFilter() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $args = [ + 'aggs' => [ + 'terms' => [] + ] + ]; + + $query_args = []; + + $query = new \WP_Query(); + + // No `ep_facet` in query_args will make it return the same array. + $this->assertSame( $args, $facet_type->set_agg_filters( $args, $query_args, $query ) ); + + // No `tax_query` in query_args will make it return the same array. + $query_args = [ + 'ep_facet' => 1, + ]; + $this->assertSame( $args, $facet_type->set_agg_filters( $args, $query_args, $query ) ); + + $query_args = [ + 'ep_facet' => 1, + 'tax_query' => [ + [ + 'taxonomy' => 'category', + 'field' => 'slug', + 'terms' => [ 'foo', 'bar' ], + + ], + ], + ]; + $changed_args = $facet_type->set_agg_filters( $args, $query_args, $query ); + $this->assertArrayHasKey( 'filter', $changed_args['aggs']['terms'] ); + } + /** * Utilitary function for the testGetSelected test. * From cfe2fc7e83d220e23c50bd03308837e20857d095 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 3 Aug 2022 11:44:26 -0300 Subject: [PATCH 06/19] sanitize GET content before using it --- .../classes/Feature/Facets/Types/Taxonomy/FacetType.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index 08dc357340..5672814ba2 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -102,9 +102,14 @@ public function get_selected() { $allowed_args = $feature->get_allowed_query_args(); $filter_name = $this->get_filter_name(); - $escaped_get_keys = array_map( 'sanitize_key', array_keys( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification - $escaped_get_keys = array_map( 'sanitize_key', array_keys( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification + $key = sanitize_key( $key ); + if ( is_array( $value ) ) { + $value = array_map( 'sanitize_text_field', $value ); + } else { + $value = sanitize_text_field( $value ); + } + if ( 0 === strpos( $key, $filter_name ) ) { $taxonomy = str_replace( $filter_name, '', $key ); From ef9e8dce1e85e906f5bb2ae663700d5bb0d6df07 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Mon, 8 Aug 2022 17:13:02 -0300 Subject: [PATCH 07/19] Move some things around --- .../blocks/facets/{ => taxonomy}/block.json | 6 +- .../js/blocks/facets/{ => taxonomy}/edit.js | 0 .../js/blocks/facets/{ => taxonomy}/index.js | 0 includes/classes/Feature/Facets/Facets.php | 119 ++++++++++++-- .../Feature/Facets/Types/Taxonomy/Block.php | 2 +- .../Facets/Types/Taxonomy/FacetType.php | 153 +++++++++--------- .../Facets/Types/Taxonomy/Renderer.php | 5 +- package.json | 2 +- 8 files changed, 191 insertions(+), 96 deletions(-) rename assets/js/blocks/facets/{ => taxonomy}/block.json (72%) rename assets/js/blocks/facets/{ => taxonomy}/edit.js (100%) rename assets/js/blocks/facets/{ => taxonomy}/index.js (100%) diff --git a/assets/js/blocks/facets/block.json b/assets/js/blocks/facets/taxonomy/block.json similarity index 72% rename from assets/js/blocks/facets/block.json rename to assets/js/blocks/facets/taxonomy/block.json index 251d8d78ed..0f0d895c73 100644 --- a/assets/js/blocks/facets/block.json +++ b/assets/js/blocks/facets/taxonomy/block.json @@ -1,7 +1,7 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 2, - "title": "Facet (ElasticPress)", + "title": "Facet by Taxonomy (ElasticPress)", "textdomain": "elasticpress", "name": "elasticpress/facet", "icon": "feedback", @@ -25,6 +25,6 @@ "supports": { "html": false }, - "editorScript": "file:/../../../../dist/js/facets-block-script.min.js", - "style": "file:/../../../../dist/css/facets-styles.min.css" + "editorScript": "file:/../../../../../dist/js/facets-block-script.min.js", + "style": "file:/../../../../../dist/css/facets-styles.min.css" } \ No newline at end of file diff --git a/assets/js/blocks/facets/edit.js b/assets/js/blocks/facets/taxonomy/edit.js similarity index 100% rename from assets/js/blocks/facets/edit.js rename to assets/js/blocks/facets/taxonomy/edit.js diff --git a/assets/js/blocks/facets/index.js b/assets/js/blocks/facets/taxonomy/index.js similarity index 100% rename from assets/js/blocks/facets/index.js rename to assets/js/blocks/facets/taxonomy/index.js diff --git a/includes/classes/Feature/Facets/Facets.php b/includes/classes/Feature/Facets/Facets.php index c085b917f7..d3552b6418 100644 --- a/includes/classes/Feature/Facets/Facets.php +++ b/includes/classes/Feature/Facets/Facets.php @@ -82,6 +82,8 @@ public function setup() { add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'front_scripts' ] ); 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' ] ); } /** @@ -115,14 +117,51 @@ public function output_feature_box_settings() { * @param array $args ES arguments * @param array $query_args Query arguments * @param WP_Query $query WP Query instance - * @since 2.5, deprecated in 4.3.0 + * @since 2.5 * @return array */ public function set_agg_filters( $args, $query_args, $query ) { - _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->set_agg_filters()" ); + // Not a facetable query + if ( empty( $query_args['ep_facet'] ) ) { + return $args; + } + + // Without taxonomies there is nothing to do here. + if ( empty( $query_args['tax_query'] ) ) { + return $args; + } + + $feature = Features::factory()->get_registered_feature( 'facets' ); + $settings = wp_parse_args( + $feature->get_settings(), + array( + 'match_type' => 'all', + ) + ); + + $facet_query_args = $query_args; - return $this->types['taxonomy']->set_agg_filters( $args, $query_args, $query ); + if ( 'any' === $settings['match_type'] ) { + foreach ( $facet_query_args['tax_query'] as $key => $taxonomy ) { + if ( is_array( $taxonomy ) ) { + unset( $facet_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( $facet_query_args[ $unwanted_arg ] ); + } + + remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_query_args, $query ); + add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + + $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; + + return $args; } /** * Output scripts for widget admin @@ -223,12 +262,42 @@ public function is_facetable( $query ) { * everywhere where a facet widget could be used. * * @param WP_Query $query WP Query - * @since 2.5, deprecated in 4.3.0 + * @since 2.5 */ public function facet_query( $query ) { - _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->facet_query()" ); + $feature = Features::factory()->get_registered_feature( 'facets' ); - return $this->types['taxonomy']->facet_query( $query ); + if ( ! $feature->is_facetable( $query ) ) { + return; + } + + /** + * Filter facet aggregations. + * + * This is used by facet types to add their own aggregations to the + * general facet. + * + * @hook ep_facet_wp_query_aggs_facet + * @since 4.3.0 + * @param {array} $facets Facets aggregations + * @return {array} New facets aggregations + */ + $facets = apply_filters( 'ep_facet_wp_query_aggs_facet', [] ); + + if ( empty( $facets ) ) { + return; + } + + $query->set( 'ep_integrate', true ); + $query->set( 'ep_facet', true ); + + $aggs = array( + 'name' => 'terms', + 'use-filter' => true, + 'aggs' => $facets, + ); + + $query->set( 'aggs', $aggs ); } /** @@ -273,13 +342,45 @@ public function get_aggs( $response, $query, $query_args, $query_object ) { /** * Get currently selected facets from query args * - * @since 2.5, deprecated in 4.3.0 + * @since 2.5 * @return array */ public function get_selected() { - _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->get_selected()" ); + $allowed_args = $this->get_allowed_query_args(); + + $filters = []; + $filter_names = []; + foreach ( $this->types as $type_obj ) { + $filter_type = $type_obj->get_filter_type(); + + $filters[ $filter_type ] = []; + $filter_names[ $filter_type ] = $type_obj->get_filter_name(); + } + + foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification + $key = sanitize_key( $key ); + if ( is_array( $value ) ) { + $value = array_map( 'sanitize_text_field', $value ); + } else { + $value = sanitize_text_field( $value ); + } + + foreach ( $filter_names as $filter_type => $filter_name ) { + if ( 0 === strpos( $key, $filter_name ) ) { + $facet = str_replace( $filter_name, '', $key ); + + $filters[ $filter_type ][ $facet ] = array( + 'terms' => array_fill_keys( array_map( 'trim', explode( ',', trim( $value, ',' ) ) ), true ), + ); + } + } + + if ( in_array( $key, $allowed_args, true ) ) { + $filters[ $key ] = $value; + } + } - return $this->types['taxonomy']->get_selected(); + return $filters; } /** diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Block.php b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php index e2160cc65b..5b5da13e9e 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Block.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php @@ -115,7 +115,7 @@ public function get_rest_facetable_taxonomies() { */ public function register_block() { register_block_type_from_metadata( - EP_PATH . 'assets/js/blocks/facets', + EP_PATH . 'assets/js/blocks/facets/taxonomy', [ 'render_callback' => [ $this, 'render_block' ], ] diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index 5672814ba2..3182eb3b00 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -30,6 +30,7 @@ public function setup() { add_action( 'widgets_init', [ $this, 'register_widgets' ] ); add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); add_action( 'pre_get_posts', [ $this, 'facet_query' ] ); + add_filter( 'ep_facet_wp_query_aggs_facet', [ $this, 'set_wp_query_aggs' ] ); $this->block = new Block(); $this->block->setup(); @@ -87,44 +88,6 @@ public function set_agg_filters( $args, $query_args, $query ) { return $args; } - /** - * Get currently selected facets from query args - * - * @return array - */ - public function get_selected() { - $feature = Features::factory()->get_registered_feature( 'facets' ); - - $filters = array( - 'taxonomies' => [], - ); - - $allowed_args = $feature->get_allowed_query_args(); - $filter_name = $this->get_filter_name(); - - foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification - $key = sanitize_key( $key ); - if ( is_array( $value ) ) { - $value = array_map( 'sanitize_text_field', $value ); - } else { - $value = sanitize_text_field( $value ); - } - - if ( 0 === strpos( $key, $filter_name ) ) { - $taxonomy = str_replace( $filter_name, '', $key ); - - $filters['taxonomies'][ $taxonomy ] = array( - 'terms' => array_fill_keys( array_map( 'trim', explode( ',', trim( $value, ',' ) ) ), true ), - ); - } - - if ( in_array( $key, $allowed_args, true ) ) { - $filters[ $key ] = $value; - } - } - - return $filters; - } /** * Build query url @@ -189,7 +152,7 @@ public function register_widgets() { * * @return string The filter name. */ - protected function get_filter_name() { + public function get_filter_name() { /** * Filter the facet filter name that's added to the URL * @@ -201,6 +164,23 @@ protected function get_filter_name() { return apply_filters( 'ep_facet_filter_name', 'ep_filter_' ); } + /** + * Get the facet filter name. + * + * @return string The filter name. + */ + public function get_filter_type() { + /** + * Filter the facet filter name that's added to the URL + * + * @hook ep_facet_filter_name + * @since 4.0.0 + * @param {string} Facet filter name + * @return {string} New facet filter name + */ + return apply_filters( 'ep_facet_filter_type', 'taxonomies' ); + } + /** * Get all taxonomies that could be selected for a facet. * @@ -247,47 +227,7 @@ public function facet_query( $query ) { return; } - $query->set( 'ep_integrate', true ); - $query->set( 'ep_facet', true ); - - $facets = []; - - /** - * Retrieve aggregations based on a custom field. This field must exist on the mapping. - * Values available out-of-the-box are: - * - slug (default) - * - term_id - * - name - * - parent - * - term_taxonomy_id - * - term_order - * - facet (retrieves a JSON representation of the term object) - * - * @since 3.6.0 - * @hook ep_facet_use_field - * @param {string} $field The term field to use - * @return {string} The chosen term field - */ - $facet_field = apply_filters( 'ep_facet_use_field', 'slug' ); - - foreach ( $taxonomies as $slug => $taxonomy ) { - $facets[ $slug ] = array( - 'terms' => array( - 'size' => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ), - 'field' => 'terms.' . $slug . '.' . $facet_field, - ), - ); - } - - $aggs = array( - 'name' => 'terms', - 'use-filter' => true, - 'aggs' => $facets, - ); - - $query->set( 'aggs', $aggs ); - - $selected_filters = $this->get_selected(); + $selected_filters = $feature->get_selected(); $settings = $feature->get_settings(); @@ -325,4 +265,57 @@ public function facet_query( $query ) { $query->set( 'tax_query', $tax_query ); } + + /** + * Add taxonomies to facets aggs + * + * @param array $facet_aggs Facet Aggs array. + * @since 4.3.0 + * @return array + */ + public function set_wp_query_aggs( $facet_aggs ) { + $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); + + /** + * Filter taxonomies made available for faceting + * + * @hook ep_facet_include_taxonomies + * @param {array} $taxonomies Taxonomies + * @return {array} New taxonomies + */ + $taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); + + if ( empty( $taxonomies ) ) { + return $facet_aggs; + } + + /** + * Retrieve aggregations based on a custom field. This field must exist on the mapping. + * Values available out-of-the-box are: + * - slug (default) + * - term_id + * - name + * - parent + * - term_taxonomy_id + * - term_order + * - facet (retrieves a JSON representation of the term object) + * + * @since 3.6.0 + * @hook ep_facet_use_field + * @param {string} $field The term field to use + * @return {string} The chosen term field + */ + $facet_field = apply_filters( 'ep_facet_use_field', 'slug' ); + + foreach ( $taxonomies as $slug => $taxonomy ) { + $facet_aggs[ $slug ] = array( + 'terms' => array( + 'size' => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ), + 'field' => 'terms.' . $slug . '.' . $facet_field, + ), + ); + } + + return $facet_aggs; + } } diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php b/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php index 205b86fc26..37fef7be98 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php @@ -77,7 +77,7 @@ public function render( $args, $instance ) { $facet_type = $feature->types['taxonomy']; - $selected_filters = $facet_type->get_selected(); + $selected_filters = $feature->get_selected(); $match_type = ( ! empty( $instance['match_type'] ) ) ? $instance['match_type'] : 'all'; @@ -160,9 +160,10 @@ public function render( $args, $instance ) { * @hook ep_facet_search_threshold * @param {int} $search_threshold Search threshold * @param {string} $taxonomy Current taxonomy + * @param {string} $context Hint about where the value will be used * @return {int} New threshold */ - $search_threshold = apply_filters( 'ep_facet_search_threshold', 15, $taxonomy ); + $search_threshold = apply_filters( 'ep_facet_search_threshold', 15, $taxonomy, 'taxonomy' ); ?>
diff --git a/package.json b/package.json index 7d49d213d0..cdfc507945 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "instant-results-admin-script.min": "./assets/js/instant-results/admin/index.js", "notice-script.min": "./assets/js/notice.js", "ordering-script.min": "./assets/js/ordering/index.js", - "facets-block-script.min": "./assets/js/blocks/facets/index.js", + "facets-block-script.min": "./assets/js/blocks/facets/taxonomy/index.js", "related-posts-block-script.min": "./assets/js/blocks/related-posts/index.js", "settings-script.min": "./assets/js/settings.js", "sync-script.min": "./assets/js/sync/index.js", From a73405d4095da3a94c472691ecc3770f90cc576f Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Mon, 15 Aug 2022 16:53:58 -0300 Subject: [PATCH 08/19] Move the new transforms.js file to the right place --- assets/js/blocks/facets/{ => taxonomy}/transforms.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename assets/js/blocks/facets/{ => taxonomy}/transforms.js (100%) diff --git a/assets/js/blocks/facets/transforms.js b/assets/js/blocks/facets/taxonomy/transforms.js similarity index 100% rename from assets/js/blocks/facets/transforms.js rename to assets/js/blocks/facets/taxonomy/transforms.js From 4b4fc216c08c79973603dbe94c58f8e11678ef8a Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Mon, 15 Aug 2022 17:17:42 -0300 Subject: [PATCH 09/19] Move some tests back --- tests/php/features/TestFacet.php | 51 ++++++++++++++++- tests/php/features/TestFacetTypeTaxonomy.php | 59 -------------------- 2 files changed, 49 insertions(+), 61 deletions(-) diff --git a/tests/php/features/TestFacet.php b/tests/php/features/TestFacet.php index a322abe7ee..e2f64d32fe 100644 --- a/tests/php/features/TestFacet.php +++ b/tests/php/features/TestFacet.php @@ -18,10 +18,40 @@ class TestFacets extends BaseTestCase { * * @since 3.6.0 * @group facets - * @expectedDeprecated ElasticPress\Feature\Facets\Facets::get_selected */ public function testGetSelected() { - Features::factory()->get_registered_feature( 'facets' )->get_selected(); + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + + parse_str( 'ep_filter_taxonomy=dolor', $_GET ); + $selected = $facet_feature->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); + + parse_str( 'ep_filter_taxonomy=dolor,sit', $_GET ); + $selected = $facet_feature->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true, 'sit' => true ], 'taxonomy', $selected ); + + parse_str( 'ep_filter_taxonomy=dolor,sit&ep_filter_othertax=amet', $_GET ); + $selected = $facet_feature->get_selected(); + + $this->assertIsArray( $selected ); + $this->assertIsArray( $selected['taxonomies'] ); + $this->assertCount( 2, $selected['taxonomies'] ); + $this->assertArrayHasKey( 'taxonomy', $selected['taxonomies'] ); + $this->assertArrayHasKey( 'othertax', $selected['taxonomies'] ); + $this->assertSame( [ 'dolor' => true, 'sit' => true ], $selected['taxonomies']['taxonomy']['terms'] ); + $this->assertSame( [ 'amet' => true ], $selected['taxonomies']['othertax']['terms'] ); + + parse_str( 's=searchterm&ep_filter_taxonomy=dolor', $_GET ); + $selected = $facet_feature->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); + $this->assertArrayHasKey( 's', $selected ); + $this->assertSame( 'searchterm', $selected['s'] ); + + parse_str( 'post_type=posttype&ep_filter_taxonomy=dolor', $_GET ); + $selected = $facet_feature->get_selected(); + $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); + $this->assertArrayHasKey( 'post_type', $selected ); + $this->assertSame( 'posttype', $selected['post_type'] ); } /** @@ -43,4 +73,21 @@ public function testBuildQueryUrl() { ]; Features::factory()->get_registered_feature( 'facets' )->build_query_url( $filters ); } + + /** + * Utilitary function for the testGetSelected test. + * + * Private as it is super specific and not likely to be extended. + * + * @param array $terms Array of terms as they should be in the $selected array. + * @param string $taxonomy Taxonomy slug + * @param array $selected Array of selected terms. + */ + private function assertSelectedTax( $terms, $taxonomy, $selected ) { + $this->assertIsArray( $selected ); + $this->assertIsArray( $selected['taxonomies'] ); + $this->assertCount( 1, $selected['taxonomies'] ); + $this->assertArrayHasKey( $taxonomy, $selected['taxonomies'] ); + $this->assertSame( $terms, $selected['taxonomies'][ $taxonomy ]['terms'] ); + } } diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 9d6d48843b..8b000dd9b9 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -14,48 +14,6 @@ */ class TestFacetTypeTaxonomy extends BaseTestCase { - /** - * Test the `get_selected` method - * - * @since 4.3.0 - * @group facets - */ - public function testGetSelected() { - $facet_feature = Features::factory()->get_registered_feature( 'facets' ); - $facet_type = $facet_feature->types['taxonomy']; - - parse_str( 'ep_filter_taxonomy=dolor', $_GET ); - $selected = $facet_type->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); - - parse_str( 'ep_filter_taxonomy=dolor,sit', $_GET ); - $selected = $facet_type->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true, 'sit' => true ], 'taxonomy', $selected ); - - parse_str( 'ep_filter_taxonomy=dolor,sit&ep_filter_othertax=amet', $_GET ); - $selected = $facet_type->get_selected(); - - $this->assertIsArray( $selected ); - $this->assertIsArray( $selected['taxonomies'] ); - $this->assertCount( 2, $selected['taxonomies'] ); - $this->assertArrayHasKey( 'taxonomy', $selected['taxonomies'] ); - $this->assertArrayHasKey( 'othertax', $selected['taxonomies'] ); - $this->assertSame( [ 'dolor' => true, 'sit' => true ], $selected['taxonomies']['taxonomy']['terms'] ); - $this->assertSame( [ 'amet' => true ], $selected['taxonomies']['othertax']['terms'] ); - - parse_str( 's=searchterm&ep_filter_taxonomy=dolor', $_GET ); - $selected = $facet_type->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); - $this->assertArrayHasKey( 's', $selected ); - $this->assertSame( 'searchterm', $selected['s'] ); - - parse_str( 'post_type=posttype&ep_filter_taxonomy=dolor', $_GET ); - $selected = $facet_type->get_selected(); - $this->assertSelectedTax( [ 'dolor' => true ], 'taxonomy', $selected ); - $this->assertArrayHasKey( 'post_type', $selected ); - $this->assertSame( 'posttype', $selected['post_type'] ); - } - /** * Test build query URL * @@ -167,21 +125,4 @@ public function testSetAggFilter() { $changed_args = $facet_type->set_agg_filters( $args, $query_args, $query ); $this->assertArrayHasKey( 'filter', $changed_args['aggs']['terms'] ); } - - /** - * Utilitary function for the testGetSelected test. - * - * Private as it is super specific and not likely to be extended. - * - * @param array $terms Array of terms as they should be in the $selected array. - * @param string $taxonomy Taxonomy slug - * @param array $selected Array of selected terms. - */ - private function assertSelectedTax( $terms, $taxonomy, $selected ) { - $this->assertIsArray( $selected ); - $this->assertIsArray( $selected['taxonomies'] ); - $this->assertCount( 1, $selected['taxonomies'] ); - $this->assertArrayHasKey( $taxonomy, $selected['taxonomies'] ); - $this->assertSame( $terms, $selected['taxonomies'][ $taxonomy ]['terms'] ); - } } From 8e24815e6db4670de07be35fd39a8ab6bc4e3438 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 16 Aug 2022 10:18:33 -0300 Subject: [PATCH 10/19] Adjust block name --- tests/cypress/integration/features/facets.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/cypress/integration/features/facets.spec.js b/tests/cypress/integration/features/facets.spec.js index 5c1ae96484..6f4e034c5a 100644 --- a/tests/cypress/integration/features/facets.spec.js +++ b/tests/cypress/integration/features/facets.spec.js @@ -27,15 +27,15 @@ describe('Facets Feature', () => { /** * Test that the Related Posts block is functional. */ - it('Can insert, configure, and use the Facet block', () => { + it('Can insert, configure, and use the Facet by Taxonomy block', () => { /** * Insert two Facets blocks. */ cy.openWidgetsPage(); cy.openBlockInserter(); - cy.getBlocksList().should('contain.text', 'Facet (ElasticPress)'); - cy.insertBlock('Facet (ElasticPress)'); - cy.insertBlock('Facet (ElasticPress)'); + cy.getBlocksList().should('contain.text', 'Facet by Taxonomy (ElasticPress)'); + cy.insertBlock('Facet by Taxonomy (ElasticPress)'); + cy.insertBlock('Facet by Taxonomy (ElasticPress)'); cy.get('.wp-block-elasticpress-facet').last().as('block'); /** From 69a41068cee8fbe17c8e47ca342a199458d358c7 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 16 Aug 2022 13:30:54 -0300 Subject: [PATCH 11/19] Make sure we only search after indexing --- .../integration/features/search/search.spec.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/cypress/integration/features/search/search.spec.js b/tests/cypress/integration/features/search/search.spec.js index bc7beda505..104194509c 100644 --- a/tests/cypress/integration/features/search/search.spec.js +++ b/tests/cypress/integration/features/search/search.spec.js @@ -134,11 +134,13 @@ describe('Post Search Feature', () => { }, }, }).then(() => { - cy.wpCli('elasticpress index --setup --yes'); - cy.visit('/?s=awesome-aluminum-shoes-variation-sku'); - cy.contains('.site-content article:nth-of-type(1) h2', 'Awesome Aluminum Shoes').should( - 'exist', - ); + cy.wpCli('elasticpress index --setup --yes').then(() => { + cy.visit('/?s=awesome-aluminum-shoes-variation-sku'); + cy.contains( + '.site-content article:nth-of-type(1) h2', + 'Awesome Aluminum Shoes', + ).should('exist'); + }); }); }); }); From a806f327c9d78dcc169ebc58e54ae25888fa8a72 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 16 Aug 2022 17:31:41 -0300 Subject: [PATCH 12/19] More adjustments + better docs --- includes/classes/Feature/Facets/Facets.php | 120 +++++++++++++----- .../Feature/Facets/Types/Taxonomy/Block.php | 14 +- .../Facets/Types/Taxonomy/FacetType.php | 85 ++----------- .../Facets/Types/Taxonomy/Renderer.php | 10 +- .../Feature/Facets/Types/Taxonomy/Widget.php | 5 +- 5 files changed, 115 insertions(+), 119 deletions(-) diff --git a/includes/classes/Feature/Facets/Facets.php b/includes/classes/Feature/Facets/Facets.php index 2aaf07403f..789ce6c2df 100644 --- a/includes/classes/Feature/Facets/Facets.php +++ b/includes/classes/Feature/Facets/Facets.php @@ -11,7 +11,6 @@ use ElasticPress\Feature as Feature; use ElasticPress\Features as Features; use ElasticPress\Utils as Utils; -use ElasticPress\FeatureRequirementsStatus as FeatureRequirementsStatus; use ElasticPress\Indexables as Indexables; if ( ! defined( 'ABSPATH' ) ) { @@ -54,6 +53,26 @@ public function __construct() { 'taxonomy' => __NAMESPACE__ . '\Types\Taxonomy\FacetType', ]; + /** + * Filter the Facet types available. + * + * ``` + * add_filter( + * 'ep_facet_types', + * function ( $types ) { + * $types['post_type'] = '\MyPlugin\PostType'; + * return $types; + * } + * ); + * ``` + * + * @since 4.3.0 + * @hook ep_facet_types + * @param {array} $types Array of types available. Keys are slugs, values are class names. + * @return {array} New array of types available + */ + $types = apply_filters( 'ep_facet_types', $types ); + foreach ( $types as $type => $class ) { $this->types[ $type ] = new $class(); } @@ -113,7 +132,10 @@ public function output_feature_box_settings() { } /** - * If we are doing or matches, we need to remove filters from aggs + * If we are doing `or` matches, we need to remove filters from aggs. + * + * By default, the same filters applied to the main query are applied to aggregations. + * If doing `or` matches, those should be removed so we get a broader set of results. * * @param array $args ES arguments * @param array $query_args Query arguments @@ -127,43 +149,30 @@ public function set_agg_filters( $args, $query_args, $query ) { return $args; } - // Without taxonomies there is nothing to do here. - if ( empty( $query_args['tax_query'] ) ) { - return $args; - } - - $feature = Features::factory()->get_registered_feature( 'facets' ); - $settings = wp_parse_args( - $feature->get_settings(), - array( - 'match_type' => 'all', - ) - ); - - $facet_query_args = $query_args; - - if ( 'any' === $settings['match_type'] ) { - foreach ( $facet_query_args['tax_query'] as $key => $taxonomy ) { - if ( is_array( $taxonomy ) ) { - unset( $facet_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( $facet_query_args[ $unwanted_arg ] ); - } + /** + * Filter WP query arguments that will be used to build the aggregations filter. + * + * The returned `$query_args` will be used to build the aggregations filter passing + * it through `Indexable\Post\Post::format_args()`. + * + * @hook ep_facet_agg_filters + * @since 4.3.0 + * @param {array} $query_args Query arguments + * @param {array} $args ES arguments + * @param {array} $query WP Query instance + * @return {array} New facets aggregations + */ + $query_args = apply_filters( 'ep_facet_agg_filters', $query_args, $args, $query ); remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_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 ); $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; return $args; } + /** * Output scripts for widget admin * @@ -387,14 +396,57 @@ public function get_selected() { /** * Build query url * - * @since 2.5, deprecated in 4.3.0 + * @since 2.5 * @param array $filters Facet filters * @return string */ public function build_query_url( $filters ) { - _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types['taxonomy']->build_query_url()" ); + $query_param = array(); + + foreach ( $this->types as $type_obj ) { + $filter_type = $type_obj->get_filter_type(); + + if ( ! empty( $filters[ $filter_type ] ) ) { + $type_filters = $filters[ $filter_type ]; + + foreach ( $type_filters as $facet => $filter ) { + if ( ! empty( $filter['terms'] ) ) { + $query_param[ $type_obj->get_filter_name() . $facet ] = implode( ',', array_keys( $filter['terms'] ) ); + } + } + } + } + + $feature = Features::factory()->get_registered_feature( 'facets' ); + $allowed_args = $feature->get_allowed_query_args(); + + if ( ! empty( $filters ) ) { + foreach ( $filters as $filter => $value ) { + if ( ! empty( $value ) && in_array( $filter, $allowed_args, true ) ) { + $query_param[ $filter ] = $value; + } + } + } + + $query_string = http_build_query( $query_param ); + + /** + * Filter facet query string + * + * @hook ep_facet_query_string + * @param {string} $query_string Current query string + * @param {array} $query_param Query parameters + * @return {string} New query string + */ + $query_string = apply_filters( 'ep_facet_query_string', $query_string, $query_param ); + + $url = $_SERVER['REQUEST_URI']; + $pagination = strpos( $url, '/page' ); + if ( false !== $pagination ) { + $url = substr( $url, 0, $pagination ); + } - return $this->types['taxonomy']->build_query_url( $filters ); + return strtok( trailingslashit( $url ), '?' ) . ( ( ! empty( $query_string ) ) ? '?' . $query_string : '' ); } /** diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Block.php b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php index 5b5da13e9e..0f86ae7f0c 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Block.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php @@ -25,7 +25,19 @@ public function setup() { add_action( 'init', [ $this, 'register_block' ] ); add_action( 'rest_api_init', [ $this, 'setup_endpoints' ] ); - $this->renderer = new Renderer(); + /** + * Filter the class name to be used to render the Facet. + * + * @since 4.3.0 + * @hook ep_facet_renderer_class + * @param {string} $classname The name of the class to be instantiated and used as a renderer. + * @param {string} $facet_type The type of the facet. + * @param {string} $context Context where the renderer will be used: `block` or `widget`, for example. + * @return {string} The name of the class + */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'block' ); + + $this->renderer = new $renderer_class(); } /** diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index 3182eb3b00..d98795dce2 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -28,7 +28,7 @@ class FacetType { */ public function setup() { add_action( 'widgets_init', [ $this, 'register_widgets' ] ); - add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); + add_filter( 'ep_facet_agg_filters', [ $this, 'agg_filters' ] ); add_action( 'pre_get_posts', [ $this, 'facet_query' ] ); add_filter( 'ep_facet_wp_query_aggs_facet', [ $this, 'set_wp_query_aggs' ] ); @@ -39,20 +39,13 @@ public function setup() { /** * If we are doing or matches, we need to remove filters from aggs * - * @param array $args ES arguments - * @param array $query_args Query arguments - * @param WP_Query $query WP Query instance + * @param array $query_args Query arguments * @return array */ - public function set_agg_filters( $args, $query_args, $query ) { - // Not a facetable query - if ( empty( $query_args['ep_facet'] ) ) { - return $args; - } - + public function agg_filters( $query_args ) { // Without taxonomies there is nothing to do here. if ( empty( $query_args['tax_query'] ) ) { - return $args; + return $query_args; } $feature = Features::factory()->get_registered_feature( 'facets' ); @@ -63,12 +56,10 @@ public function set_agg_filters( $args, $query_args, $query ) { ) ); - $facet_query_args = $query_args; - if ( 'any' === $settings['match_type'] ) { - foreach ( $facet_query_args['tax_query'] as $key => $taxonomy ) { + foreach ( $query_args['tax_query'] as $key => $taxonomy ) { if ( is_array( $taxonomy ) ) { - unset( $facet_query_args['tax_query'][ $key ] ); + unset( $query_args['tax_query'][ $key ] ); } } } @@ -76,68 +67,10 @@ public function set_agg_filters( $args, $query_args, $query ) { // @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( $facet_query_args[ $unwanted_arg ] ); - } - - remove_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - $facet_formatted_args = Indexables::factory()->get( 'post' )->format_args( $facet_query_args, $query ); - add_filter( 'ep_post_formatted_args', [ $this, 'set_agg_filters' ], 10, 3 ); - - $args['aggs']['terms']['filter'] = $facet_formatted_args['post_filter']; - - return $args; - } - - - /** - * Build query url - * - * @param array $filters Facet filters - * @return string - */ - public function build_query_url( $filters ) { - $query_param = array(); - - if ( ! empty( $filters['taxonomies'] ) ) { - $tax_filters = $filters['taxonomies']; - - foreach ( $tax_filters as $taxonomy => $filter ) { - if ( ! empty( $filter['terms'] ) ) { - $query_param[ $this->get_filter_name() . $taxonomy ] = implode( ',', array_keys( $filter['terms'] ) ); - } - } - } - - $feature = Features::factory()->get_registered_feature( 'facets' ); - $allowed_args = $feature->get_allowed_query_args(); - - if ( ! empty( $filters ) ) { - foreach ( $filters as $filter => $value ) { - if ( ! empty( $value ) && in_array( $filter, $allowed_args, true ) ) { - $query_param[ $filter ] = $value; - } - } + unset( $query_args[ $unwanted_arg ] ); } - $query_string = http_build_query( $query_param ); - - /** - * Filter facet query string - * - * @hook ep_facet_query_string - * @param {string} $query_string Current query string - * @param {array} $query_param Query parameters - * @return {string} New query string - */ - $query_string = apply_filters( 'ep_facet_query_string', $query_string, $query_param ); - - $url = $_SERVER['REQUEST_URI']; - $pagination = strpos( $url, '/page' ); - if ( false !== $pagination ) { - $url = substr( $url, 0, $pagination ); - } - - return strtok( trailingslashit( $url ), '?' ) . ( ( ! empty( $query_string ) ) ? '?' . $query_string : '' ); + return $query_args; } /** @@ -174,7 +107,7 @@ public function get_filter_type() { * Filter the facet filter name that's added to the URL * * @hook ep_facet_filter_name - * @since 4.0.0 + * @since 4.3.0 * @param {string} Facet filter name * @return {string} New facet filter name */ diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php b/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php index 37fef7be98..1bd852ee4f 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Renderer.php @@ -75,12 +75,8 @@ public function render( $args, $instance ) { } } - $facet_type = $feature->types['taxonomy']; - $selected_filters = $feature->get_selected(); - $match_type = ( ! empty( $instance['match_type'] ) ) ? $instance['match_type'] : 'all'; - /** * Get all the terms so we know if we should output the widget */ @@ -196,7 +192,7 @@ public function render( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_facet_term_html( $term, - $facet_type->build_query_url( $new_filters ), + $feature->build_query_url( $new_filters ), true ); // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped @@ -259,7 +255,7 @@ public function render( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_facet_term_html( $term, - $facet_type->build_query_url( $new_filters ), + $feature->build_query_url( $new_filters ), $selected ); // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped @@ -288,7 +284,7 @@ public function render( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->get_facet_term_html( $term, - $facet_type->build_query_url( $new_filters ) + $feature->build_query_url( $new_filters ) ); // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped endforeach; diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php b/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php index 256a35197b..f8defff3df 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php @@ -36,7 +36,10 @@ public function __construct() { parent::__construct( 'ep-facet', esc_html__( 'ElasticPress - Facet', 'elasticpress' ), $options ); - $this->renderer = new Renderer(); + /** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'widget' ); + + $this->renderer = new $renderer_class(); } /** From 7bc36885d8a6d3711e4c6c6e5b08732ced72a824 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 16 Aug 2022 18:35:56 -0300 Subject: [PATCH 13/19] Move some tests around --- tests/php/features/TestFacet.php | 51 ++++++++++++++- tests/php/features/TestFacetTypeTaxonomy.php | 68 -------------------- 2 files changed, 49 insertions(+), 70 deletions(-) diff --git a/tests/php/features/TestFacet.php b/tests/php/features/TestFacet.php index e2f64d32fe..7f3deb7c35 100644 --- a/tests/php/features/TestFacet.php +++ b/tests/php/features/TestFacet.php @@ -59,9 +59,10 @@ public function testGetSelected() { * * @since 3.6.0 * @group facets - * @expectedDeprecated ElasticPress\Feature\Facets\Facets::build_query_url */ public function testBuildQueryUrl() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $filters = [ 'taxonomies' => [ 'category' => [ @@ -71,7 +72,53 @@ public function testBuildQueryUrl() { ] ] ]; - Features::factory()->get_registered_feature( 'facets' )->build_query_url( $filters ); + + $this->assertEquals( '/?ep_filter_category=augue', $facet_feature->build_query_url( $filters ) ); + + $filters['s'] = 'dolor'; + $this->assertEquals( '/?ep_filter_category=augue&s=dolor', $facet_feature->build_query_url( $filters ) ); + + unset( $filters['s'] ); + $filters = [ + 'taxonomies' => [ + 'category' => [ + 'terms' => [ + 'augue' => 1, + 'consectetur' => 1 + ] + ] + ] + ]; + + $this->assertEquals( '/?ep_filter_category=augue%2Cconsectetur', $facet_feature->build_query_url( $filters ) ); + + $_SERVER['REQUEST_URI'] = 'test/page/1'; + + $filters['s'] = 'dolor'; + $this->assertEquals( 'test/?ep_filter_category=augue%2Cconsectetur&s=dolor', $facet_feature->build_query_url( $filters ) ); + + /** + * Test the `ep_facet_query_string` filter. + */ + $change_facet_query_string = function ( $query_string, $query_params ) { + $this->assertIsArray( $query_params ); + $query_string .= '&foobar'; + return $query_string; + }; + add_filter( 'ep_facet_query_string', $change_facet_query_string, 10, 2 ); + $this->assertStringEndsWith( '&foobar', $facet_feature->build_query_url( $filters ) ); + remove_filter( 'ep_facet_query_string', $change_facet_query_string, 10, 2 ); + + /** + * (Indirectly) test the `ep_facet_filter_name` filter + */ + $change_ep_facet_filter_name = function( $original_name ) { + $this->assertEquals( 'ep_filter_', $original_name ); + return 'ep_custom_filter_'; + }; + add_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); + $this->assertEquals( 'test/?ep_custom_filter_category=augue%2Cconsectetur&s=dolor', $facet_feature->build_query_url( $filters ) ); + remove_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); } /** diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 8b000dd9b9..74e03f4aa0 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -14,74 +14,6 @@ */ class TestFacetTypeTaxonomy extends BaseTestCase { - /** - * Test build query URL - * - * @since 4.3.0 - * @group facets - */ - public function testBuildQueryUrl() { - $facet_feature = Features::factory()->get_registered_feature( 'facets' ); - $facet_type = $facet_feature->types['taxonomy']; - - $filters = [ - 'taxonomies' => [ - 'category' => [ - 'terms' => [ - 'augue' => 1 - ] - ] - ] - ]; - - $this->assertEquals( '/?ep_filter_category=augue', $facet_type->build_query_url( $filters ) ); - - $filters['s'] = 'dolor'; - $this->assertEquals( '/?ep_filter_category=augue&s=dolor', $facet_type->build_query_url( $filters ) ); - - unset( $filters['s'] ); - $filters = [ - 'taxonomies' => [ - 'category' => [ - 'terms' => [ - 'augue' => 1, - 'consectetur' => 1 - ] - ] - ] - ]; - - $this->assertEquals( '/?ep_filter_category=augue%2Cconsectetur', $facet_type->build_query_url( $filters ) ); - - $_SERVER['REQUEST_URI'] = 'test/page/1'; - - $filters['s'] = 'dolor'; - $this->assertEquals( 'test/?ep_filter_category=augue%2Cconsectetur&s=dolor', $facet_type->build_query_url( $filters ) ); - - /** - * Test the `ep_facet_query_string` filter. - */ - $change_facet_query_string = function ( $query_string, $query_params ) { - $this->assertIsArray( $query_params ); - $query_string .= '&foobar'; - return $query_string; - }; - add_filter( 'ep_facet_query_string', $change_facet_query_string, 10, 2 ); - $this->assertStringEndsWith( '&foobar', $facet_type->build_query_url( $filters ) ); - remove_filter( 'ep_facet_query_string', $change_facet_query_string, 10, 2 ); - - /** - * (Indirectly) test the `ep_facet_filter_name` filter - */ - $change_ep_facet_filter_name = function( $original_name ) { - $this->assertEquals( 'ep_filter_', $original_name ); - return 'ep_custom_filter_'; - }; - add_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); - $this->assertEquals( 'test/?ep_custom_filter_category=augue%2Cconsectetur&s=dolor', $facet_type->build_query_url( $filters ) ); - remove_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); - } - /** * Test set_agg_filters * From 176276e17b943f68cf8ce3ed977793be558b4d5c Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 16 Aug 2022 18:40:25 -0300 Subject: [PATCH 14/19] Moving more tests --- tests/php/features/TestFacet.php | 43 +++++++++++++++++++ tests/php/features/TestFacetTypeTaxonomy.php | 44 -------------------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/tests/php/features/TestFacet.php b/tests/php/features/TestFacet.php index 7f3deb7c35..21324bed82 100644 --- a/tests/php/features/TestFacet.php +++ b/tests/php/features/TestFacet.php @@ -121,6 +121,49 @@ public function testBuildQueryUrl() { remove_filter( 'ep_facet_filter_name', $change_ep_facet_filter_name ); } + /** + * Test set_agg_filters + * + * @since 4.3.0 + * @group facets + */ + public function testSetAggFilter() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + + $args = [ + 'aggs' => [ + 'terms' => [] + ] + ]; + + $query_args = []; + + $query = new \WP_Query(); + + // No `ep_facet` in query_args will make it return the same array. + $this->assertSame( $args, $facet_feature->set_agg_filters( $args, $query_args, $query ) ); + + // No `tax_query` in query_args will make it return the same array. + $query_args = [ + 'ep_facet' => 1, + ]; + $this->assertSame( $args, $facet_feature->set_agg_filters( $args, $query_args, $query ) ); + + $query_args = [ + 'ep_facet' => 1, + 'tax_query' => [ + [ + 'taxonomy' => 'category', + 'field' => 'slug', + 'terms' => [ 'foo', 'bar' ], + + ], + ], + ]; + $changed_args = $facet_feature->set_agg_filters( $args, $query_args, $query ); + $this->assertArrayHasKey( 'filter', $changed_args['aggs']['terms'] ); + } + /** * Utilitary function for the testGetSelected test. * diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 74e03f4aa0..8e6d44d715 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -13,48 +13,4 @@ * Facets\Types\Taxonomy\FacetType test class */ class TestFacetTypeTaxonomy extends BaseTestCase { - - /** - * Test set_agg_filters - * - * @since 4.3.0 - * @group facets - */ - public function testSetAggFilter() { - $facet_feature = Features::factory()->get_registered_feature( 'facets' ); - $facet_type = $facet_feature->types['taxonomy']; - - $args = [ - 'aggs' => [ - 'terms' => [] - ] - ]; - - $query_args = []; - - $query = new \WP_Query(); - - // No `ep_facet` in query_args will make it return the same array. - $this->assertSame( $args, $facet_type->set_agg_filters( $args, $query_args, $query ) ); - - // No `tax_query` in query_args will make it return the same array. - $query_args = [ - 'ep_facet' => 1, - ]; - $this->assertSame( $args, $facet_type->set_agg_filters( $args, $query_args, $query ) ); - - $query_args = [ - 'ep_facet' => 1, - 'tax_query' => [ - [ - 'taxonomy' => 'category', - 'field' => 'slug', - 'terms' => [ 'foo', 'bar' ], - - ], - ], - ]; - $changed_args = $facet_type->set_agg_filters( $args, $query_args, $query ); - $this->assertArrayHasKey( 'filter', $changed_args['aggs']['terms'] ); - } } From 08d6f60a2eade192dd14f10745663b0f5e4de044 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 17 Aug 2022 14:45:21 -0300 Subject: [PATCH 15/19] Fix and add tests --- tests/php/features/TestFacet.php | 32 ++++---- tests/php/features/TestFacetTypeTaxonomy.php | 78 ++++++++++++++++++++ 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/tests/php/features/TestFacet.php b/tests/php/features/TestFacet.php index 21324bed82..097ad9f6b9 100644 --- a/tests/php/features/TestFacet.php +++ b/tests/php/features/TestFacet.php @@ -143,25 +143,23 @@ public function testSetAggFilter() { // No `ep_facet` in query_args will make it return the same array. $this->assertSame( $args, $facet_feature->set_agg_filters( $args, $query_args, $query ) ); - // No `tax_query` in query_args will make it return the same array. - $query_args = [ - 'ep_facet' => 1, + /** + * Without any function hooked to `ep_facet_agg_filters` we expect + * aggregation filters to matche exactly the filter applied to the main + * query. + */ + remove_all_filters( 'ep_facet_agg_filters' ); + $query_args = [ + 'ep_facet' => 1, + 'post_type' => 'post', + 'post_status' => 'publish', ]; - $this->assertSame( $args, $facet_feature->set_agg_filters( $args, $query_args, $query ) ); + // Get the ES query args. + $formatted_args = \ElasticPress\Indexables::factory()->get( 'post' )->format_args( $query_args, $query ); + // Get the ES query args after applying the changes to aggs filters. + $formatted_args_with_args = $facet_feature->set_agg_filters( $formatted_args, $query_args, $query ); - $query_args = [ - 'ep_facet' => 1, - 'tax_query' => [ - [ - 'taxonomy' => 'category', - 'field' => 'slug', - 'terms' => [ 'foo', 'bar' ], - - ], - ], - ]; - $changed_args = $facet_feature->set_agg_filters( $args, $query_args, $query ); - $this->assertArrayHasKey( 'filter', $changed_args['aggs']['terms'] ); + $this->assertSame( $formatted_args['post_filter'], $formatted_args_with_args['aggs']['terms']['filter'] ); } /** diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 8e6d44d715..cd56281f06 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -13,4 +13,82 @@ * Facets\Types\Taxonomy\FacetType test class */ class TestFacetTypeTaxonomy extends BaseTestCase { + /** + * Test agg_filters + * + * @since 4.3.0 + * @group facets + */ + public function testAggFilters() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $query_args = []; + $this->assertSame( $query_args, $facet_type->agg_filters( $query_args ) ); + + $query_args = [ + 'tax_query' => [ + [ + 'taxonomy' => 'category', + 'terms' => [ 1, 2, 3 ], + ], + [ + 'taxonomy' => 'post_tag', + 'terms' => [ 4, 5, 6 ], + ], + ], + ]; + + /** + * Test when `match_type` is `all`. In this case, all the filters applied to the + * main query should be applied to aggregations as well. + */ + $set_facet_match_type_all = function() { + return [ + 'facets' => [ + 'match_type' => 'all', + ], + ]; + }; + add_filter( 'pre_site_option_ep_feature_settings', $set_facet_match_type_all ); + add_filter( 'pre_option_ep_feature_settings', $set_facet_match_type_all ); + + $this->assertSame( $query_args, $facet_type->agg_filters( $query_args ) ); + + remove_filter( 'pre_site_option_ep_feature_settings', $set_facet_match_type_all ); + remove_filter( 'pre_option_ep_feature_settings', $set_facet_match_type_all ); + + /** + * Test when `match_type` is `any`. In this case, the code should remove + * from the aggregations filter the taxonomy filters applied to the main query. + */ + $set_facet_match_type_any = function() { + return [ + 'facets' => [ + 'match_type' => 'any', + ], + ]; + }; + add_filter( 'pre_site_option_ep_feature_settings', $set_facet_match_type_any ); + add_filter( 'pre_option_ep_feature_settings', $set_facet_match_type_any ); + + $this->assertSame( [ 'tax_query' => [] ], $facet_type->agg_filters( $query_args ) ); + + remove_filter( 'pre_site_option_ep_feature_settings', $set_facet_match_type_any ); + remove_filter( 'pre_option_ep_feature_settings', $set_facet_match_type_any ); + + /** + * Test the removal of unwanted parameters. + */ + $query_args = [ + 'category_name' => 'lorem', + 'cat' => 'lorem', + 'tag' => 'lorem', + 'tag_id' => 'lorem', + 'taxonomy' => 'lorem', + 'term' => 'lorem', + 'tax_query' => [ [] ], + ]; + $this->assertSame( [ 'tax_query' => [ [] ] ], $facet_type->agg_filters( $query_args ) ); + } } From 94cb4f5fef76d28e10a3e09099997095c142145b Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 17 Aug 2022 15:22:48 -0300 Subject: [PATCH 16/19] An abstract class just to keep it organized --- includes/classes/Feature/Facets/FacetType.php | 37 +++++++++++++++++++ includes/classes/Feature/Facets/Facets.php | 4 +- .../Facets/Types/Taxonomy/FacetType.php | 17 ++++----- 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 includes/classes/Feature/Facets/FacetType.php diff --git a/includes/classes/Feature/Facets/FacetType.php b/includes/classes/Feature/Facets/FacetType.php new file mode 100644 index 0000000000..fa83f35ee2 --- /dev/null +++ b/includes/classes/Feature/Facets/FacetType.php @@ -0,0 +1,37 @@ + $class ) { - $this->types[ $type ] = new $class(); + if ( is_a( $class, __NAMESPACE__ . '\FacetType', true ) ) { + $this->types[ $type ] = new $class(); + } } parent::__construct(); diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index d98795dce2..171b9bdea7 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -9,12 +9,11 @@ namespace ElasticPress\Feature\Facets\Types\Taxonomy; use \ElasticPress\Features; -use \ElasticPress\Indexables; /** * Taxonomy facet type class */ -class FacetType { +class FacetType extends \ElasticPress\Feature\Facets\FacetType { /** * Block instance. @@ -85,7 +84,7 @@ public function register_widgets() { * * @return string The filter name. */ - public function get_filter_name() { + public function get_filter_name() : string { /** * Filter the facet filter name that's added to the URL * @@ -98,18 +97,18 @@ public function get_filter_name() { } /** - * Get the facet filter name. + * Get the facet filter type. * * @return string The filter name. */ - public function get_filter_type() { + public function get_filter_type() : string { /** - * Filter the facet filter name that's added to the URL + * Filter the facet filter type. Used by the Facet feature to organize filters. * - * @hook ep_facet_filter_name + * @hook ep_facet_filter_type * @since 4.3.0 - * @param {string} Facet filter name - * @return {string} New facet filter name + * @param {string} Facet filter type + * @return {string} New facet filter type */ return apply_filters( 'ep_facet_filter_type', 'taxonomies' ); } From 727651ed61551ee5f76065bc7a515edca2af1bee Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 24 Aug 2022 17:32:37 -0300 Subject: [PATCH 17/19] Move renderer creation so we can pass more info to the ep_facet_renderer_class filter --- .../Feature/Facets/Types/Taxonomy/Block.php | 37 +++++++++++-------- .../Feature/Facets/Types/Taxonomy/Widget.php | 17 +++++---- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Block.php b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php index 0f86ae7f0c..7186775202 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Block.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Block.php @@ -24,20 +24,6 @@ class Block { public function setup() { add_action( 'init', [ $this, 'register_block' ] ); add_action( 'rest_api_init', [ $this, 'setup_endpoints' ] ); - - /** - * Filter the class name to be used to render the Facet. - * - * @since 4.3.0 - * @hook ep_facet_renderer_class - * @param {string} $classname The name of the class to be instantiated and used as a renderer. - * @param {string} $facet_type The type of the facet. - * @param {string} $context Context where the renderer will be used: `block` or `widget`, for example. - * @return {string} The name of the class - */ - $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'block' ); - - $this->renderer = new $renderer_class(); } /** @@ -141,10 +127,25 @@ public function register_block() { */ public function render_block( $attributes ) { $attributes = $this->parse_attributes( $attributes ); + + /** + * Filter the class name to be used to render the Facet. + * + * @since 4.3.0 + * @hook ep_facet_renderer_class + * @param {string} $classname The name of the class to be instantiated and used as a renderer. + * @param {string} $facet_type The type of the facet. + * @param {string} $context Context where the renderer will be used: `block` or `widget`, for example. + * @param {string} $attributes Element attributes. + * @return {string} The name of the class + */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'block', $attributes ); + $renderer = new $renderer_class(); + ob_start(); ?>
- renderer->render( [], $attributes ); ?> + render( [], $attributes ); ?>
renderer->render( [], $attributes ); + $renderer->render( [], $attributes ); $block_content = ob_get_clean(); if ( empty( $block_content ) ) { diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php b/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php index f8defff3df..fd52a50df4 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/Widget.php @@ -35,11 +35,6 @@ public function __construct() { ); parent::__construct( 'ep-facet', esc_html__( 'ElasticPress - Facet', 'elasticpress' ), $options ); - - /** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */ - $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'widget' ); - - $this->renderer = new $renderer_class(); } /** @@ -50,7 +45,11 @@ public function __construct() { * @since 2.5, 4.2.0 made a wrapper for the renderer call. */ public function widget( $args, $instance ) { - $this->renderer->render( $args, $instance ); + /** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'block', $instance ); + $renderer = new $renderer_class(); + + $renderer->render( $args, $instance ); } /** @@ -65,7 +64,11 @@ public function widget( $args, $instance ) { protected function get_facet_term_html( $term, $url, $selected = false ) { _deprecated_function( __FUNCTION__, '4.2.0', '$this->renderer->get_facet_term_html()' ); - return $this->renderer->get_facet_term_html( $term, $url, $selected ); + /** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'taxonomy', 'block', [] ); + $renderer = new $renderer_class(); + + return $renderer->get_facet_term_html( $term, $url, $selected ); } /** From 54a7219e9acb2cb603deffe93a98a1791bd431ec Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Thu, 25 Aug 2022 13:05:12 -0300 Subject: [PATCH 18/19] Use get_facetable_taxonomies more often + move ep_facet_use_field so we have more context --- .../Facets/Types/Taxonomy/FacetType.php | 59 +++++++------------ 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php index 171b9bdea7..2aec603149 100644 --- a/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Taxonomy/FacetType.php @@ -144,16 +144,7 @@ public function facet_query( $query ) { return; } - $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); - - /** - * Filter taxonomies made available for faceting - * - * @hook ep_facet_include_taxonomies - * @param {array} $taxonomies Taxonomies - * @return {array} New taxonomies - */ - $taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); + $taxonomies = $this->get_facetable_taxonomies(); if ( empty( $taxonomies ) ) { return; @@ -206,40 +197,32 @@ public function facet_query( $query ) { * @return array */ public function set_wp_query_aggs( $facet_aggs ) { - $taxonomies = get_taxonomies( array( 'public' => true ), 'object' ); - - /** - * Filter taxonomies made available for faceting - * - * @hook ep_facet_include_taxonomies - * @param {array} $taxonomies Taxonomies - * @return {array} New taxonomies - */ - $taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies ); + $taxonomies = $this->get_facetable_taxonomies(); if ( empty( $taxonomies ) ) { return $facet_aggs; } - /** - * Retrieve aggregations based on a custom field. This field must exist on the mapping. - * Values available out-of-the-box are: - * - slug (default) - * - term_id - * - name - * - parent - * - term_taxonomy_id - * - term_order - * - facet (retrieves a JSON representation of the term object) - * - * @since 3.6.0 - * @hook ep_facet_use_field - * @param {string} $field The term field to use - * @return {string} The chosen term field - */ - $facet_field = apply_filters( 'ep_facet_use_field', 'slug' ); - foreach ( $taxonomies as $slug => $taxonomy ) { + /** + * Retrieve aggregations based on a custom field. This field must exist on the mapping. + * Values available out-of-the-box are: + * - slug (default) + * - term_id + * - name + * - parent + * - term_taxonomy_id + * - term_order + * - facet (retrieves a JSON representation of the term object) + * + * @since 3.6.0, 4.3.0 added $taxonomy + * @hook ep_facet_use_field + * @param {string} $field The term field to use + * @param {WP_Taxonomy} $taxonomy The taxonomy + * @return {string} The chosen term field + */ + $facet_field = apply_filters( 'ep_facet_use_field', 'slug', $taxonomy ); + $facet_aggs[ $slug ] = array( 'terms' => array( 'size' => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ), From c313c4a56592c6345b55363d8aec8d71672eee28 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Thu, 25 Aug 2022 13:05:33 -0300 Subject: [PATCH 19/19] Some phpunit tests to check filters usage --- tests/php/features/TestFacet.php | 30 ++++ tests/php/features/TestFacetTypeTaxonomy.php | 140 +++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/tests/php/features/TestFacet.php b/tests/php/features/TestFacet.php index 097ad9f6b9..3f6d720e92 100644 --- a/tests/php/features/TestFacet.php +++ b/tests/php/features/TestFacet.php @@ -13,6 +13,36 @@ * Facet test class */ class TestFacets extends BaseTestCase { + /** + * Test facet type registration + * + * @since 4.3.0 + * @group facets + */ + public function testFacetTypeRegistration() { + $facet_type = $this->getMockForAbstractClass( '\ElasticPress\Feature\Facets\FacetType' ); + $facet_type->expects( $this->exactly( 1 ) )->method( 'setup' ); + + $register_facet_type = function( $types ) use ( $facet_type ) { + $types['test_custom'] = get_class( $facet_type ); + return $types; + }; + + add_filter( 'ep_facet_types', $register_facet_type ); + + $facets = new \ElasticPress\Feature\Facets\Facets(); + + $this->assertArrayHasKey( 'test_custom', $facets->types ); + $this->assertInstanceOf( get_class( $facet_type ), $facets->types['test_custom'] ); + + // Make sure it uses our instance + $facets->types['test_custom'] = $facet_type; + + $facets->setup(); + + remove_filter( 'ep_facet_types', $register_facet_type ); + } + /** * Test the `get_selected` method * diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index cd56281f06..cd27bb7e86 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -13,6 +13,146 @@ * Facets\Types\Taxonomy\FacetType test class */ class TestFacetTypeTaxonomy extends BaseTestCase { + /** + * Test get_filter_name + * + * @since 4.3.0 + * @group facets + */ + public function testGetFilterName() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + /** + * Test default behavior + */ + $this->assertEquals( 'ep_filter_', $facet_type->get_filter_name() ); + + /** + * Test the `ep_facet_filter_name` filter + */ + $change_filter_name = function( $filter_name ) { + return $filter_name . '_'; + }; + add_filter( 'ep_facet_filter_name', $change_filter_name ); + $this->assertEquals( 'ep_filter__', $facet_type->get_filter_name() ); + remove_filter( 'ep_facet_filter_name', $change_filter_name ); + } + + /** + * Test get_filter_type + * + * @since 4.3.0 + * @group facets + */ + public function testGetFilterType() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + /** + * Test default behavior + */ + $this->assertEquals( 'taxonomies', $facet_type->get_filter_type() ); + + /** + * Test the `ep_facet_filter_type` filter + */ + $change_filter_type = function( $filter_type ) { + return $filter_type . '_'; + }; + add_filter( 'ep_facet_filter_type', $change_filter_type ); + $this->assertEquals( 'taxonomies_', $facet_type->get_filter_type() ); + remove_filter( 'ep_facet_filter_type', $change_filter_type ); + } + + /** + * Test get_facetable_taxonomies + * + * @since 4.3.0 + * @group facets + */ + public function testGetFacetableTaxonomies() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $public_taxonomies = array_keys( get_taxonomies( array( 'public' => true ), 'names' ) ); + $facetable_taxonomies = array_keys( $facet_type->get_facetable_taxonomies() ); + + /** + * Test default behavior + */ + $this->assertEqualsCanonicalizing( $public_taxonomies, $facetable_taxonomies ); + $this->assertContains( 'category', $facetable_taxonomies ); + + /** + * Test the `ep_facet_include_taxonomies` filter + */ + $change_facetable_taxonomies = function( $taxonomies ) { + unset( $taxonomies['category'] ); + return $taxonomies; + }; + add_filter( 'ep_facet_include_taxonomies', $change_facetable_taxonomies ); + + $facetable_taxonomies = array_keys( $facet_type->get_facetable_taxonomies() ); + $this->assertNotContains( 'category', $facetable_taxonomies ); + + remove_filter( 'ep_facet_include_taxonomies', $change_facetable_taxonomies ); + } + + /** + * Test set_wp_query_aggs + * + * @since 4.3.0 + * @group facets + */ + public function testSetWpQueryAggs() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $with_aggs = $facet_type->set_wp_query_aggs( [] ); + + /** + * Test default behavior + */ + $default_cat_agg = [ + 'terms' => [ + 'size' => 10000, + 'field' => 'terms.category.slug', + ], + ]; + $this->assertSame( $with_aggs['category'], $default_cat_agg ); + + /** + * Test the `ep_facet_use_field` filter + */ + $change_cat_facet_field = function( $field, $taxonomy ) { + return ( 'category' === $taxonomy->name ) ? 'term_id' : $field; + }; + + add_filter( 'ep_facet_use_field', $change_cat_facet_field, 10, 2 ); + + $with_aggs = $facet_type->set_wp_query_aggs( [] ); + $this->assertSame( 'terms.category.term_id', $with_aggs['category']['terms']['field'] ); + $this->assertSame( 'terms.post_tag.slug', $with_aggs['post_tag']['terms']['field'] ); + + remove_filter( 'ep_facet_use_field', $change_cat_facet_field ); + + /** + * Test the `ep_facet_taxonomies_size` filter + */ + $change_tax_bucket_size = function( $size, $taxonomy ) { + return ( 'category' === $taxonomy->name ) ? 5 : $size; + }; + + add_filter( 'ep_facet_taxonomies_size', $change_tax_bucket_size, 10, 2 ); + + $with_aggs = $facet_type->set_wp_query_aggs( [] ); + $this->assertSame( 5, $with_aggs['category']['terms']['size'] ); + $this->assertSame( 10000, $with_aggs['post_tag']['terms']['size'] ); + + remove_filter( 'ep_facet_taxonomies_size', $change_tax_bucket_size ); + } + /** * Test agg_filters *