slug = 'facets'; $this->title = esc_html__( 'Facets', 'elasticpress' ); $this->summary = __( 'Add controls to your website to filter content by one or more taxonomies.', 'elasticpress' ); $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#facets', 'elasticpress' ); $this->requires_install_reindex = false; $this->default_settings = [ 'match_type' => 'all', ]; $types = [ '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 ) { if ( is_a( $class, __NAMESPACE__ . '\FacetType', true ) ) { $this->types[ $type ] = new $class(); } } parent::__construct(); } /** * Setup hooks and filters for feature * * @since 2.5 */ public function setup() { global $pagenow; // This feature should not run while in the editor. if ( in_array( $pagenow, [ 'post-new.php', 'post.php' ], true ) ) { return; } foreach ( $this->types as $type => $class ) { $this->types[ $type ]->setup(); } add_filter( 'widget_types_to_hide_from_legacy_widget_block', [ $this, 'hide_legacy_widget' ] ); add_action( 'ep_valid_response', [ $this, 'get_aggs' ], 10, 4 ); 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' ] ); } /** * Dashboard facet settings * * @since 2.5 */ public function output_feature_box_settings() { $settings = $this->get_settings(); if ( ! $settings ) { $settings = []; } $settings = wp_parse_args( $settings, $this->default_settings ); ?>

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 * * @param string $hook WP hook * @since 2.5 */ public function admin_scripts( $hook ) { if ( 'widgets.php' !== $hook ) { return; } wp_enqueue_style( 'elasticpress-facets-admin', EP_URL . 'dist/css/facets-admin-styles.min.css', Utils\get_asset_info( 'facets-admin-styles', 'dependencies' ), Utils\get_asset_info( 'facets-admin-styles', 'version' ) ); } /** * Output front end facets styles * * @since 2.5 */ public function front_scripts() { wp_register_script( 'elasticpress-facets', EP_URL . 'dist/js/facets-script.min.js', Utils\get_asset_info( 'facets-script', 'dependencies' ), Utils\get_asset_info( 'facets-script', 'version' ), true ); wp_register_style( 'elasticpress-facets', EP_URL . 'dist/css/facets-styles.min.css', Utils\get_asset_info( 'facets-styles', 'dependencies' ), Utils\get_asset_info( 'facets-styles', 'version' ) ); } /** * Figure out if we can/should facet the query * * @param WP_Query $query WP Query * @since 2.5 * @return bool */ public function is_facetable( $query ) { /** * Bypass the standard checks and set a query to be facetable * * @hook ep_is_facetable * @param {bool} $bypass Defaults to false. * @param {WP_Query} $query The current WP_Query. * @return {bool} true to bypass, false to ignore */ if ( \apply_filters( 'ep_is_facetable', false, $query ) ) { return true; } if ( is_admin() || is_feed() ) { return false; } if ( defined( 'WP_CLI' ) && WP_CLI ) { return false; } if ( ! $query->is_main_query() ) { return false; } $ep_integrate = $query->get( 'ep_integrate', null ); if ( false === $ep_integrate ) { return false; } $woocommerce = Features::factory()->get_registered_feature( 'woocommerce' ); if ( ! $woocommerce->is_active() && ( function_exists( 'is_product_category' ) && is_product_category() ) ) { return false; } if ( ! $this->is_facetable_page( $query ) ) { return false; } return true; } /** * 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 * @since 2.5 */ public function facet_query( $query ) { $feature = Features::factory()->get_registered_feature( 'facets' ); 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 ); } /** * Hacky. Save aggregation data for later in a global * * @param array $response ES response * @param array $query Prepared Elasticsearch query * @param array $query_args Current WP Query arguments * @param mixed $query_object Could be WP_Query, WP_User_Query, etc. * @since 2.5 */ public function get_aggs( $response, $query, $query_args, $query_object ) { if ( empty( $query_object ) || 'WP_Query' !== get_class( $query_object ) || ! $this->is_facetable( $query_object ) ) { return; } $GLOBALS['ep_facet_aggs'] = false; if ( ! empty( $response['aggregations'] ) ) { $GLOBALS['ep_facet_aggs'] = []; if ( isset( $response['aggregations']['terms'] ) && is_array( $response['aggregations']['terms'] ) ) { foreach ( $response['aggregations']['terms'] as $key => $agg ) { if ( 'doc_count' === $key ) { continue; } if ( ! is_array( $agg ) || empty( $agg['buckets'] ) ) { continue; } $GLOBALS['ep_facet_aggs'][ $key ] = []; foreach ( $agg['buckets'] as $bucket ) { $GLOBALS['ep_facet_aggs'][ $key ][ $bucket['key'] ] = $bucket['doc_count']; } } } } } /** * Get currently selected facets from query args * * @since 2.5 * @return array */ public function 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 $filters; } /** * Build query url * * @since 2.5 * @param array $filters Facet filters * @return string */ public function build_query_url( $filters ) { $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 strtok( trailingslashit( $url ), '?' ) . ( ( ! empty( $query_string ) ) ? '?' . $query_string : '' ); } /** * Register facet widget(s) * * @since 2.5, deprecated in 4.3.0 */ public function register_widgets() { _deprecated_function( __METHOD__, '4.3.0', "\ElasticPress\Features::factory()->get_registered_feature( 'facets' )->types[ \$type ]->register_widgets()" ); } /** * Hide the legacy widget. * * Hides the legacy widget in favor of the Block when the block editor * is in use and the legacy widget has not been used. * * @since 4.3 * @param array $widgets An array of excluded widget-type IDs. * @return array array of excluded widget-type IDs to hide. */ public function hide_legacy_widget( $widgets ) { $widgets[] = 'ep-facet'; return $widgets; } /** * Output feature box long * * @since 2.5 */ public function output_feature_box_long() { ?>

Facet widget that administrators can add to the website's sidebars (widgetized areas), so that visitors can filter applicable content and search results by one or more taxonomy terms.", 'elasticpress' ), esc_url( admin_url( 'widgets.php' ) ) ) ); ?>

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, deprecated in 4.3.0 * @return array */ public function get_facetable_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(); } /** * Figure out if Facet widget can display on page. * * @param WP_Query $query WP Query * @since 4.2.1 * @return bool */ protected function is_facetable_page( $query ) { return $query->is_home() || $query->is_search() || $query->is_tax() || $query->is_tag() || $query->is_category() || $query->is_post_type_archive(); } }