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 );
+ ?>
+
+ 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 );
+ }
}