diff --git a/assets/js/blocks/facets/meta-range/block.json b/assets/js/blocks/facets/meta-range/block.json new file mode 100644 index 0000000000..da7145621e --- /dev/null +++ b/assets/js/blocks/facets/meta-range/block.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "title": "Facet by Meta Range (ElasticPress)", + "textdomain": "elasticpress", + "name": "elasticpress/facet-meta-range", + "icon": "feedback", + "category": "widgets", + "attributes": { + "facet": { + "type": "string", + "default": "" + } + }, + "supports": { + "html": false + }, + "editorScript": "ep-facets-meta-range-block-script", + "style": "file:/../../../../../dist/css/facets-styles.css" +} \ No newline at end of file diff --git a/assets/js/blocks/facets/meta-range/edit.js b/assets/js/blocks/facets/meta-range/edit.js new file mode 100644 index 0000000000..1dbc42d8e4 --- /dev/null +++ b/assets/js/blocks/facets/meta-range/edit.js @@ -0,0 +1,81 @@ +/* global facetMetaBlock */ + +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { PanelBody, Spinner, Placeholder, SelectControl } from '@wordpress/components'; +import { + Fragment, + useEffect, + useState, + useCallback, + createInterpolateElement, +} from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; +import { __ } from '@wordpress/i18n'; + +const FacetBlockEdit = (props) => { + const { attributes, setAttributes } = props; + const [metaKeys, setMetaKeys] = useState([]); + const [preview, setPreview] = useState(''); + const [loading, setLoading] = useState(false); + const { facet, min, max } = attributes; + + const blockProps = useBlockProps(); + + const load = useCallback(async () => { + const metaKeys = await apiFetch({ + path: '/elasticpress/v1/facets/meta-range/keys', + }); + setMetaKeys(metaKeys); + }, [setMetaKeys]); + + useEffect(load, [load]); + + useEffect(() => { + setLoading(true); + const params = new URLSearchParams({ facet }); + apiFetch({ + path: `/elasticpress/v1/facets/meta-range/block-preview?${params}`, + }) + .then((preview) => setPreview(preview)) + .finally(() => setLoading(false)); + }, [facet, min, max]); + + return ( + + + + sync your content', + 'elasticpress', + ), + // eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label + { a: }, + )} + value={facet} + options={[ + ...metaKeys.map((metaKey) => ({ + label: metaKey, + value: metaKey, + })), + ]} + onChange={(value) => setAttributes({ facet: value })} + /> + + + +
+ {loading && ( + + + + )} + {/* eslint-disable-next-line react/no-danger */} + {!loading &&
} +
+ + ); +}; +export default FacetBlockEdit; diff --git a/assets/js/blocks/facets/meta-range/index.js b/assets/js/blocks/facets/meta-range/index.js new file mode 100644 index 0000000000..f2e7dac9cd --- /dev/null +++ b/assets/js/blocks/facets/meta-range/index.js @@ -0,0 +1,15 @@ +/** + * WordPress dependencies. + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies. + */ +import edit from './edit'; +import block from './block.json'; + +registerBlockType(block, { + edit, + save: () => {}, +}); diff --git a/includes/classes/Feature/Facets/FacetType.php b/includes/classes/Feature/Facets/FacetType.php index 36aca5814c..dad760d37e 100644 --- a/includes/classes/Feature/Facets/FacetType.php +++ b/includes/classes/Feature/Facets/FacetType.php @@ -53,4 +53,40 @@ public function get_sanitize_callback() : string { return apply_filters( 'ep_facet_default_sanitize_callback', 'sanitize_text_field' ); } + /** + * Format selected values. + * + * @since 4.5.0 + * @param string $facet Facet name + * @param mixed $value Facet value + * @param array $filters Selected filters + * @return array + */ + public function format_selected( string $facet, $value, array $filters ) { + $terms = explode( ',', trim( $value, ',' ) ); + $filters[ $this->get_filter_type() ][ $facet ] = [ + 'terms' => array_fill_keys( array_map( $this->get_sanitize_callback(), $terms ), true ), + ]; + return $filters; + } + + /** + * Add selected filters to the query string. + * + * @since 4.5.0 + * @param array $query_params Existent query parameters + * @param array $filters Selected filters + * @return array + */ + public function add_query_params( array $query_params, array $filters ) : array { + $selected = $filters[ $this->get_filter_type() ]; + + foreach ( $selected as $facet => $filter ) { + if ( ! empty( $filter['terms'] ) ) { + $query_params[ $this->get_filter_name() . $facet ] = implode( ',', array_keys( $filter['terms'] ) ); + } + } + + return $query_params; + } } diff --git a/includes/classes/Feature/Facets/Facets.php b/includes/classes/Feature/Facets/Facets.php index 84d3f105b1..24159b122a 100644 --- a/includes/classes/Feature/Facets/Facets.php +++ b/includes/classes/Feature/Facets/Facets.php @@ -351,12 +351,17 @@ public function get_aggs( $response, $query, $query_args, $query_object ) { continue; } - if ( ! is_array( $agg ) || empty( $agg['buckets'] ) ) { + if ( ! is_array( $agg ) || ( empty( $agg['buckets'] ) && empty( $agg['value'] ) ) ) { continue; } $GLOBALS['ep_facet_aggs'][ $key ] = []; + if ( ! empty( $agg['value'] ) ) { + $GLOBALS['ep_facet_aggs'][ $key ] = $agg['value']; + continue; + } + foreach ( $agg['buckets'] as $bucket ) { $GLOBALS['ep_facet_aggs'][ $key ][ $bucket['key'] ] = $bucket['doc_count']; } @@ -374,29 +379,20 @@ public function get_aggs( $response, $query, $query_args, $query_object ) { public function get_selected() { $allowed_args = $this->get_allowed_query_args(); - $filters = []; - $filter_names = []; - $sanitize_callbacks = []; + $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(); - $sanitize_callbacks[ $filter_type ] = $type_obj->get_sanitize_callback(); + $filter_names[ $type_obj->get_filter_name() ] = $type_obj; } foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification $key = sanitize_key( $key ); - foreach ( $filter_names as $filter_type => $filter_name ) { + foreach ( $filter_names as $filter_name => $type_obj ) { if ( 0 === strpos( $key, $filter_name ) ) { - $facet = str_replace( $filter_name, '', $key ); - $sanitize_callback = $sanitize_callbacks[ $filter_type ]; - $terms = explode( ',', trim( $value, ',' ) ); + $facet = str_replace( $filter_name, '', $key ); - $filters[ $filter_type ][ $facet ] = array( - 'terms' => array_fill_keys( array_map( $sanitize_callback, $terms ), true ), - ); + $filters = $type_obj->format_selected( $facet, $value, $filters ); } } @@ -416,20 +412,13 @@ public function get_selected() { * @return string */ public function build_query_url( $filters ) { - $query_param = array(); + $query_params = 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'] ) ); - } - } + if ( empty( $filters[ $type_obj->get_filter_type() ] ) ) { + continue; } + $query_params = $type_obj->add_query_params( $query_params, $filters ); } $feature = Features::factory()->get_registered_feature( 'facets' ); @@ -438,22 +427,22 @@ public function build_query_url( $filters ) { if ( ! empty( $filters ) ) { foreach ( $filters as $filter => $value ) { if ( in_array( $filter, $allowed_args, true ) ) { - $query_param[ $filter ] = $value; + $query_params[ $filter ] = $value; } } } - $query_string = build_query( $query_param ); + $query_string = build_query( $query_params ); /** * Filter facet query string * * @hook ep_facet_query_string * @param {string} $query_string Current query string - * @param {array} $query_param Query parameters + * @param {array} $query_params Query parameters * @return {string} New query string */ - $query_string = apply_filters( 'ep_facet_query_string', $query_string, $query_param ); + $query_string = apply_filters( 'ep_facet_query_string', $query_string, $query_params ); $url = $_SERVER['REQUEST_URI']; $pagination = strpos( $url, '/page' ); diff --git a/includes/classes/Feature/Facets/Types/Meta/FacetType.php b/includes/classes/Feature/Facets/Types/Meta/FacetType.php index ed60ee0cea..f6ac0a0516 100644 --- a/includes/classes/Feature/Facets/Types/Meta/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Meta/FacetType.php @@ -251,7 +251,10 @@ public function get_facets_meta_fields() { continue; } - if ( false === strpos( $instance['content'], 'elasticpress/facet-meta' ) ) { + if ( + false !== strpos( $instance['content'], 'elasticpress/facet-meta-range' ) + || false === strpos( $instance['content'], 'elasticpress/facet-meta' ) + ) { continue; } diff --git a/includes/classes/Feature/Facets/Types/MetaRange/Block.php b/includes/classes/Feature/Facets/Types/MetaRange/Block.php new file mode 100644 index 0000000000..0c2c880710 --- /dev/null +++ b/includes/classes/Feature/Facets/Types/MetaRange/Block.php @@ -0,0 +1,218 @@ + [ $this, 'render_block' ], + ] + ); + + wp_localize_script( 'ep-facets-meta-range-block-script', 'facetMetaBlock', [ 'syncUrl' => Utils\get_sync_url() ] ); + } + + + /** + * Setup REST endpoints for the feature. + */ + public function setup_endpoints() { + register_rest_route( + 'elasticpress/v1', + 'facets/meta-range/keys', + [ + 'methods' => 'GET', + 'permission_callback' => [ $this, 'check_facets_meta_rest_permission' ], + 'callback' => [ $this, 'get_rest_registered_metakeys' ], + ] + ); + register_rest_route( + 'elasticpress/v1', + 'facets/meta-range/block-preview', + [ + 'methods' => 'GET', + 'permission_callback' => [ $this, 'check_facets_meta_rest_permission' ], + 'callback' => [ $this, 'render_block_preview' ], + 'args' => [ + 'facet' => [ + 'sanitize_callback' => 'sanitize_text_field', + ], + ], + + ] + ); + } + + /** + * Check permissions of the /facets/taxonomies REST endpoint. + * + * @return WP_Error|true + */ + public function check_facets_meta_rest_permission() { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new \WP_Error( 'ep_rest_forbidden', esc_html__( 'Sorry, you cannot view this resource.', 'elasticpress' ), array( 'status' => 401 ) ); + } + + return true; + } + + /** + * Render the block. + * + * @param array $attributes Block attributes. + */ + public function render_block( $attributes ) { + $attributes = $this->parse_attributes( $attributes ); + + /** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'meta-range', 'block', $attributes ); + $renderer = new $renderer_class(); + + ob_start(); + ?> +
+ render( [], $attributes ); ?> +
+ get_registered_feature( 'search' ); + + $attributes = $this->parse_attributes( + [ + 'facet' => $request->get_param( 'facet' ), + 'is_preview' => true, + ] + ); + + add_filter( + 'ep_facet_meta_fields', + function ( $meta_fields ) use ( $attributes ) { + $meta_fields = [ $attributes['facet'] ]; + return $meta_fields; + } + ); + + $wp_query = new \WP_Query( + [ + 'post_type' => $search->get_searchable_post_types(), + 'per_page' => 1, + ] + ); + + /** This filter is documented in includes/classes/Feature/Facets/Types/Taxonomy/Block.php */ + $renderer_class = apply_filters( 'ep_facet_renderer_class', __NAMESPACE__ . '\Renderer', 'meta-block', 'block', $attributes ); + $renderer = new $renderer_class(); + + ob_start(); + $renderer->render( [], $attributes ); + $block_content = ob_get_clean(); + + if ( empty( $block_content ) ) { + return sprintf( + /* translators: Meta field name */ + esc_html__( 'Preview for %s not available', 'elasticpress' ), + esc_html( $request->get_param( 'facet' ) ) + ); + } + + $block_content = preg_replace( '/href="(.*?)"/', 'href="#"', $block_content ); + return '
' . $block_content . '
'; + } + + /** + * Return an array of registered meta keys. + * + * @return array + */ + public function get_rest_registered_metakeys() { + $post_indexable = \ElasticPress\Indexables::factory()->get( 'post' ); + + try { + $meta_keys = $post_indexable->get_distinct_meta_field_keys(); + } catch ( \Throwable $th ) { + $meta_keys = []; + } + + return $meta_keys; + } + + /** + * Utilitary method to set default attributes. + * + * @param array $attributes Attributes passed + * @return array + */ + protected function parse_attributes( $attributes ) { + $attributes = wp_parse_args( + $attributes, + [ + 'facet' => '', + 'is_preview' => false, + ] + ); + if ( empty( $attributes['facet'] ) ) { + $registered_metakeys = $this->get_rest_registered_metakeys(); + if ( ! empty( $registered_metakeys ) ) { + $attributes['facet'] = reset( $registered_metakeys ); + } + } + return $attributes; + } +} diff --git a/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php b/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php new file mode 100644 index 0000000000..39e2a695c1 --- /dev/null +++ b/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php @@ -0,0 +1,225 @@ +block = new Block(); + $this->block->setup(); + } + + /** + * Get the facet filter name. + * + * @return string The filter name. + */ + public function get_filter_name() : string { + /** + * Filter the facet filter name that's added to the URL + * + * @hook ep_facet_meta_range_filter_name + * @since 4.5.0 + * @param {string} Facet filter name + * @return {string} New facet filter name + */ + return apply_filters( 'ep_facet_meta_range_filter_name', 'ep_meta_range_filter_' ); + } + + /** + * Get the facet filter type. + * + * @return string The filter name. + */ + public function get_filter_type() : string { + /** + * Filter the facet filter type. Used by the Facet feature to organize filters. + * + * @hook ep_facet_meta_range_filter_type + * @since 4.5.0 + * @param {string} Facet filter type + * @return {string} New facet filter type + */ + return apply_filters( 'ep_facet_meta_range_filter_type', 'meta-range' ); + } + + /** + * Add selected filters to the Facet filter in the ES query + * + * @param array $filters Current Facet filters + * @return array + */ + public function add_query_filters( $filters ) { + $feature = Features::factory()->get_registered_feature( 'facets' ); + + $all_selected_filters = $feature->get_selected(); + if ( empty( $all_selected_filters ) || empty( $all_selected_filters[ $this->get_filter_type() ] ) ) { + return $filters; + } + + $selected_range_filters = $all_selected_filters[ $this->get_filter_type() ]; + + $range_filters = []; + foreach ( $selected_range_filters as $field_name => $values ) { + foreach ( $values as $min_or_max => $value ) { + $operator = '_min' === $min_or_max ? 'gte' : 'lte'; + + $range_filters[ $field_name ][ $operator ] = floatval( $value ); + } + } + + foreach ( $range_filters as $field_name => $range_filter ) { + $filters[] = [ + 'range' => [ + 'meta.' . $field_name . '.double' => $range_filter, + ], + ]; + } + + return $filters; + } + + /** + * Add meta fields to facets aggs + * + * @param array $facet_aggs Facet Aggs array. + * @return array + */ + public function set_wp_query_aggs( $facet_aggs ) { + $facets_meta_fields = $this->get_facets_meta_fields(); + + foreach ( $facets_meta_fields as $meta_field ) { + /** + * Retrieve aggregations based on a custom field. This field must exist on the mapping and be numeric + * so ES can apply min and max to it. + * + * `meta..value` is *not* available, as that throws a `Fielddata is disabled on text fields by default` error. + * + * @since 4.5.0 + * @hook ep_facet_meta_range_use_field + * @param {string} $es_field The Elasticsearch field to use for this meta field + * @param {string} $meta_field The meta field key + * @return {string} The chosen ES field + */ + $facet_field = apply_filters( 'ep_facet_meta_range_use_field', 'double', $meta_field ); + + $facet_aggs[ $this->get_filter_name() . $meta_field . '_min' ] = array( + 'min' => array( + 'field' => 'meta.' . $meta_field . '.' . $facet_field, + ), + ); + + $facet_aggs[ $this->get_filter_name() . $meta_field . '_max' ] = array( + 'max' => array( + 'field' => 'meta.' . $meta_field . '.' . $facet_field, + ), + ); + } + + return $facet_aggs; + } + + /** + * Format selected values. + * + * For meta range facets, we will have `[ 'facet_name' => [ '_min' => X, '_max' => Y ] ]`; + * + * @since 4.5.0 + * @param string $facet Facet name + * @param mixed $value Facet value + * @param array $filters Selected filters + * @return array + */ + public function format_selected( string $facet, $value, array $filters ) { + $min_or_max = substr( $facet, -4 ); + $field_name = substr( $facet, 0, -4 ); + + $filters[ $this->get_filter_type() ][ $field_name ][ $min_or_max ] = $value; + + return $filters; + } + + /** + * Add selected filters to the query string. + * + * @since 4.5.0 + * @param array $query_params Existent query parameters + * @param array $filters Selected filters + * @return array + */ + public function add_query_params( array $query_params, array $filters ) : array { + $selected = $filters[ $this->get_filter_type() ]; + + foreach ( $selected as $facet => $values ) { + foreach ( $values as $min_or_max => $value ) { + $query_params[ $this->get_filter_name() . $facet . $min_or_max ] = $value; + } + } + + return $query_params; + } + + /** + * Get all fields selected in all Facet blocks + * + * @return array + */ + public function get_facets_meta_fields() { + $facets_meta_fields = []; + + $widget_block_instances = ( new \WP_Widget_Block() )->get_settings(); + foreach ( $widget_block_instances as $instance ) { + if ( ! isset( $instance['content'] ) ) { + continue; + } + + if ( false === strpos( $instance['content'], 'elasticpress/facet-meta-range' ) ) { + continue; + } + + if ( ! preg_match_all( '/"facet":"(.*?)"/', $instance['content'], $matches ) ) { + continue; + } + + $facets_meta_fields = array_merge( $facets_meta_fields, $matches[1] ); + } + + /** + * Filter meta fields to be used in aggregations related to meta range blocks. + * + * @since 4.5.0 + * @hook ep_facet_meta_range_fields + * @param {string} $facets_meta_fields Array of meta field keys + * @return {string} The array of meta field keys + */ + return apply_filters( 'ep_facet_meta_range_fields', $facets_meta_fields ); + } +} diff --git a/includes/classes/Feature/Facets/Types/MetaRange/Renderer.php b/includes/classes/Feature/Facets/Types/MetaRange/Renderer.php new file mode 100644 index 0000000000..0fabd2c72d --- /dev/null +++ b/includes/classes/Feature/Facets/Types/MetaRange/Renderer.php @@ -0,0 +1,119 @@ +meta_field = $instance['facet']; + if ( empty( $this->meta_field ) ) { + if ( $instance['is_preview'] ) { + esc_html_e( 'Preview not available. Make sure you select a field.', 'elasticpress' ); + } + return false; + } + + if ( ! $this->should_render() ) { + return; + } + + $feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $feature->types['meta-range']; + + $min_field_name = $facet_type->get_filter_name() . $this->meta_field . '_min'; + $max_field_name = $facet_type->get_filter_name() . $this->meta_field . '_max'; + + if ( empty( $GLOBALS['ep_facet_aggs'][ $min_field_name ] ) + || empty( $GLOBALS['ep_facet_aggs'][ $max_field_name ] ) + ) { + if ( $instance['is_preview'] ) { + esc_html_e( 'Could not get min and max values. Is this a numeric field?', 'elasticpress' ); + } + return false; + } + + $min = $GLOBALS['ep_facet_aggs'][ $min_field_name ]; + $max = $GLOBALS['ep_facet_aggs'][ $max_field_name ]; + + $selected_min_value = null; + $selected_max_value = null; + + $selected_filters = $feature->get_selected(); + foreach ( $selected_filters[ $facet_type->get_filter_type() ] as $filter => $values ) { + if ( $this->meta_field !== $filter ) { + continue; + } + + $selected_min_value = $values['_min'] ?? null; + $selected_max_value = $values['_max'] ?? null; + unset( $selected_filters[ $facet_type->get_filter_type() ][ $filter ] ); + } + $form_action = wp_parse_url( $feature->build_query_url( $selected_filters ) ); + wp_parse_str( $form_action['query'] ?? '', $filter_fields ); + ?> +
+ $value ) { ?> + + + + + +
+

From to

+ get_registered_feature( 'facets' ); + if ( $wp_query->get( 'ep_facet', false ) && ! $feature->is_facetable( $wp_query ) ) { + return false; + } + + $es_success = ( ! empty( $wp_query->elasticsearch_success ) ) ? true : false; + if ( ! $es_success ) { + return false; + } + + return true; + } +} diff --git a/package.json b/package.json index ae0228b40d..dbbb937470 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "ordering-script": "./assets/js/ordering/index.js", "facets-block-script": "./assets/js/blocks/facets/taxonomy/index.js", "facets-meta-block-script": "./assets/js/blocks/facets/meta/index.js", + "facets-meta-range-block-script": "./assets/js/blocks/facets/meta-range/index.js", "related-posts-block-script": "./assets/js/blocks/related-posts/index.js", "settings-script": "./assets/js/settings.js", "sync-script": "./assets/js/sync/index.js", diff --git a/tests/php/features/TestFacetTypeMetaRange.php b/tests/php/features/TestFacetTypeMetaRange.php new file mode 100644 index 0000000000..d4d13cc15e --- /dev/null +++ b/tests/php/features/TestFacetTypeMetaRange.php @@ -0,0 +1,228 @@ +get_registered_feature( 'facets' ); + if ( ! isset( $facet_feature->types['meta-range'] ) && class_exists( '\ElasticPress\Feature\Facets\Types\MetaRange\FacetType' ) ) { + $facet_feature->types['meta-range'] = new \ElasticPress\Feature\Facets\Types\MetaRange\FacetType(); + $facet_feature->types['meta-range']->setup(); + } + + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $this->facet_type = $facet_feature->types['meta-range']; + + parent::set_up(); + } + + /** + * Test get_filter_name + * + * @group facets + */ + public function testGetFilterName() { + /** + * Test default behavior + */ + $this->assertEquals( 'ep_meta_range_filter_', $this->facet_type->get_filter_name() ); + + /** + * Test the `ep_facet_meta_filter_name` filter + */ + $change_filter_name = function( $filter_name ) { + return $filter_name . '_'; + }; + add_filter( 'ep_facet_meta_range_filter_name', $change_filter_name ); + $this->assertEquals( 'ep_meta_range_filter__', $this->facet_type->get_filter_name() ); + } + + /** + * Test get_filter_type + * + * @group facets + */ + public function testGetFilterType() { + /** + * Test default behavior + */ + $this->assertEquals( 'meta-range', $this->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_meta_range_filter_type', $change_filter_type ); + $this->assertEquals( 'meta-range_', $this->facet_type->get_filter_type() ); + } + + /** + * Test add_query_filters + * + * @group facets + */ + public function testAddQueryFilters() { + parse_str( 'ep_meta_range_filter_my_custom_field_min=5&ep_meta_range_filter_my_custom_field_max=25', $_GET ); + + $new_filters = $this->facet_type->add_query_filters( [] ); + $expected = [ + [ + 'range' => [ + 'meta.my_custom_field.double' => [ + 'gte' => floatval( 5 ), + 'lte' => floatval( 25 ), + ], + ], + ], + ]; + $this->assertSame( $expected, $new_filters ); + } + + /** + * Test set_wp_query_aggs + * + * @group facets + */ + public function testSetWpQueryAggs() { + $set_facet_meta_field = function() { + return [ 'new_meta_key_1', 'new_meta_key_2' ]; + }; + add_filter( 'ep_facet_meta_range_fields', $set_facet_meta_field ); + + $with_aggs = $this->facet_type->set_wp_query_aggs( [] ); + + /** + * Test default behavior + */ + $default_min_agg = [ + 'min' => [ + 'field' => 'meta.new_meta_key_1.double', + ], + ]; + $this->assertSame( $with_aggs['ep_meta_range_filter_new_meta_key_1_min'], $default_min_agg ); + $default_max_agg = [ + 'max' => [ + 'field' => 'meta.new_meta_key_1.double', + ], + ]; + $this->assertSame( $with_aggs['ep_meta_range_filter_new_meta_key_1_max'], $default_max_agg ); + + /** + * Test the `ep_facet_meta_use_field` filter + */ + $change_meta_facet_field = function( $es_field, $meta_field ) { + return ( 'new_meta_key_1' === $meta_field ) ? 'long' : $es_field; + }; + + add_filter( 'ep_facet_meta_use_field', $change_meta_facet_field, 10, 2 ); + + $with_aggs = $this->facet_type->set_wp_query_aggs( [] ); + $this->assertSame( 'meta.new_meta_key_1.double', $with_aggs['ep_meta_range_filter_new_meta_key_1_min']['min']['field'] ); + $this->assertSame( 'meta.new_meta_key_1.double', $with_aggs['ep_meta_range_filter_new_meta_key_1_max']['max']['field'] ); + $this->assertSame( 'meta.new_meta_key_2.double', $with_aggs['ep_meta_range_filter_new_meta_key_2_min']['min']['field'] ); + $this->assertSame( 'meta.new_meta_key_2.double', $with_aggs['ep_meta_range_filter_new_meta_key_2_max']['max']['field'] ); + + remove_filter( 'ep_facet_meta_use_field', $change_meta_facet_field ); + } + + /** + * Test the format_selected method. + */ + public function testFormatSelected() { + $original_filters = [ 'custom_type' => [ 'facet' => [ 1, 2, 3 ] ] ]; + $new_filters = $this->facet_type->format_selected( 'my_meta_min', '5', $original_filters ); + $expected_filters = array_merge( + $original_filters, + [ + $this->facet_type->get_filter_type() => [ + 'my_meta' => [ + '_min' => '5', + ], + ], + ] + ); + + $this->assertSame( $new_filters, $expected_filters ); + + /** + * Analyzing tags=slug3,slug4 should ADD tags, keeping the category index. + */ + $original_filters = $expected_filters; + $new_filters = $this->facet_type->format_selected( 'my_meta_max', '25', $original_filters ); + + $expected_filters[ $this->facet_type->get_filter_type() ]['my_meta'] = [ + '_min' => '5', + '_max' => '25', + ]; + + $this->assertSame( $new_filters, $expected_filters ); + } + + /** + * Test the add_query_params method. + */ + public function testAddQueryParams() { + $original_query_params = [ 'custom_name' => 'custom_value' ]; + $selected_filters = [ + [ + 'custom_type' => [ 'facet' => [ 1, 2, 3 ] ] + ], + $this->facet_type->get_filter_type() => [ + 'my_meta_1' => [ + '_min' => '5', + '_max' => '25', + ], + 'my_meta_2' => [ + '_min' => '10', + '_max' => '50', + ], + ], + ]; + + $new_query_params = $this->facet_type->add_query_params( $original_query_params, $selected_filters ); + $expected_query_params = array_merge( + $original_query_params, + [ + $this->facet_type->get_filter_name() . 'my_meta_1_min' => '5', + $this->facet_type->get_filter_name() . 'my_meta_1_max' => '25', + $this->facet_type->get_filter_name() . 'my_meta_2_min' => '10', + $this->facet_type->get_filter_name() . 'my_meta_2_max' => '50', + ] + ); + + $this->assertSame( $new_query_params, $expected_query_params ); + } + + /** + * Test get_facets_meta_fields + * + * @group facets + */ + public function testGetFacetsMetaFields() { + $this->markTestIncomplete(); + } +} diff --git a/tests/php/features/TestFacetTypeTaxonomy.php b/tests/php/features/TestFacetTypeTaxonomy.php index 1457de457b..5947264b7a 100644 --- a/tests/php/features/TestFacetTypeTaxonomy.php +++ b/tests/php/features/TestFacetTypeTaxonomy.php @@ -201,7 +201,6 @@ public function testAddQueryFilters() { * @group facets */ public function testGetSanitizeCallback() { - $facet_feature = Features::factory()->get_registered_feature( 'facets' ); $test_taxonomy = 'This is a test taxonomy'; @@ -228,4 +227,91 @@ public function testGetSanitizeCallback() { $expected_result = sanitize_text_field( $test_taxonomy ); $this->assertArrayHasKey( $expected_result, $selected['taxonomies']['taxonomy']['terms'] ); } + + /** + * Test the format_selected method. + * + * @todo Move this to a mock, as it is just inherited now + * @since 4.5.0 + */ + public function testFormatSelected() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $original_filters = [ 'custom_type' => [ 'facet' => [ 1, 2, 3 ] ] ]; + $new_filters = $facet_type->format_selected( 'category', 'slug1,slug2', $original_filters ); + $expected_filters = array_merge( + $original_filters, + [ + $facet_type->get_filter_type() => [ + 'category' => [ + 'terms' => [ + 'slug1' => true, + 'slug2' => true, + ], + ], + ], + ] + ); + + $this->assertSame( $new_filters, $expected_filters ); + + /** + * Analyzing tags=slug3,slug4 should ADD tags, keeping the category index. + */ + $original_filters = $expected_filters; + $new_filters = $facet_type->format_selected( 'tags', 'slug3,slug4', $original_filters ); + + $expected_filters[ $facet_type->get_filter_type() ]['tags'] = [ + 'terms' => [ + 'slug3' => true, + 'slug4' => true, + ], + ]; + + $this->assertSame( $new_filters, $expected_filters ); + } + + /** + * Test the add_query_params method. + * + * @todo Move this to a mock, as it is just inherited now + * @since 4.5.0 + */ + public function testAddQueryParams() { + $facet_feature = Features::factory()->get_registered_feature( 'facets' ); + $facet_type = $facet_feature->types['taxonomy']; + + $original_query_params = [ 'custom_name' => 'custom_value' ]; + $selected_filters = [ + [ + 'custom_type' => [ 'facet' => [ 1, 2, 3 ] ] + ], + $facet_type->get_filter_type() => [ + 'category' => [ + 'terms' => [ + 'slug1' => true, + 'slug2' => true, + ], + ], + 'tags' => [ + 'terms' => [ + 'slug3' => true, + 'slug4' => true, + ], + ], + ], + ]; + + $new_query_params = $facet_type->add_query_params( $original_query_params, $selected_filters ); + $expected_query_params = array_merge( + $original_query_params, + [ + $facet_type->get_filter_name() . 'category' => 'slug1,slug2', + $facet_type->get_filter_name() . 'tags' => 'slug3,slug4', + ] + ); + + $this->assertSame( $new_query_params, $expected_query_params ); + } }