slug = 'did-you-mean'; $this->group = 'core-search'; $this->requires_install_reindex = true; $this->available_during_installation = true; $this->default_settings = [ 'search_behavior' => '0', ]; $this->requires_feature = 'search'; parent::__construct(); } /** * Sets i18n strings. * * @return void * @since 5.2.0 */ public function set_i18n_strings(): void { $this->title = esc_html__( 'Did You Mean', 'elasticpress' ); $this->summary = '
' . __( '"Did You Mean" search feature provides alternative suggestions for misspelled or ambiguous search queries, enhancing search accuracy and user experience. To display suggestions in your theme, please follow this tutorial.', 'elasticpress' ) . '
'; $this->docs_url = __( 'https://www.elasticpress.io/resources/articles/did-you-mean/', 'elasticpress' ); } /** * Setup search functionality. * * @return void */ public function setup() { add_filter( 'ep_post_mapping', [ $this, 'add_mapping' ] ); add_filter( 'ep_post_formatted_args', [ $this, 'add_query_args' ], 10, 3 ); add_filter( 'ep_integrate_search_queries', [ $this, 'set_ep_suggestion' ], 10, 2 ); add_action( 'template_redirect', [ $this, 'automatically_redirect_user' ] ); add_action( 'ep_suggestions', [ $this, 'the_output' ] ); } /** * Add mapping. * * @param array $mapping Post mapping. */ public function add_mapping( $mapping ): array { // Shingle token filter. $mapping['settings']['analysis']['filter']['shingle_filter'] = [ 'type' => 'shingle', 'min_shingle_size' => 2, 'max_shingle_size' => 3, ]; // Custom analyzer. $mapping['settings']['analysis']['analyzer']['trigram'] = [ 'type' => 'custom', 'tokenizer' => 'standard', 'filter' => [ 'lowercase', 'shingle_filter', ], ]; if ( version_compare( (string) Elasticsearch::factory()->get_elasticsearch_version(), '7.0', '<' ) ) { $mapping['mappings']['post']['properties']['post_content']['fields'] = [ 'shingle' => [ 'type' => 'text', 'analyzer' => 'trigram', ], ]; } else { $mapping['mappings']['properties']['post_content']['fields'] = [ 'shingle' => [ 'type' => 'text', 'analyzer' => 'trigram', ], ]; } return $mapping; } /** * Return the suggested search term. * * @param WP_Query $query WP_Query object * @return string|false */ public function get_suggestion( $query = null ) { global $wp_query; $settings = $this->get_settings(); if ( empty( $settings['active'] ) ) { return false; } if ( ! $query && $wp_query->is_main_query() && $wp_query->is_search() ) { $query = $wp_query; } if ( ! is_a( $query, '\WP_Query' ) ) { return false; } $term = $this->get_suggested_term( $query ); if ( empty( $term ) ) { return false; } $html = sprintf( '%s: %s?', esc_html__( 'Did you mean', 'elasticpress' ), get_search_link( $term ), $term ); $html .= $this->get_alternatives_terms( $query ); $terms = $query->suggested_terms['options'] ?? []; /** * Filter the did you mean suggested HTML. * * @since 4.6.0 * @hook ep_suggestion_html * @param {string} $html The HTML output. * @param {array} $terms All suggested terms. * @param {WP_Query} $query The WP_Query object. * @return {string} New HTML output */ return apply_filters( 'ep_suggestion_html', $html, $terms, $query ); } /** * If needed set the `suggest` to ES query clause. * * @param array $formatted_args Formatted Elasticsearch query. * @param array $args WP_Query arguments * @param array $wp_query WP_Query object */ public function add_query_args( $formatted_args, $args, $wp_query ): array { $search_analyzer = [ 'phrase' => [ 'field' => 'post_content.shingle', 'max_errors' => 2, 'direct_generator' => [ [ 'field' => 'post_content.shingle', ], ], ], ]; /** * Filter the search analyzer use for the did you mean feature. * * @since 4.6.0 * @hook ep_search_suggestion_analyzer * @param {array} $search_analyzer Search analyzer * @param {array} $formatted_args Formatted Elasticsearch query * @param {array} $args WP_Query arguments * @param {WP_Query} $wp_query WP_Query object * @return {array} New search analyzer */ $search_analyzer = apply_filters( 'ep_search_suggestion_analyzer', $search_analyzer, $formatted_args, $args, $wp_query ); if ( ! empty( $args['s'] ) ) { $formatted_args['suggest'] = array( 'text' => $args['s'], 'ep_suggestion' => $search_analyzer, ); } return $formatted_args; } /** * Set the ep_suggestion flag to true if the query is a search query. * * @param bool $enabled Whether to enable the search queries integration. * @param WP_Query $query The WP_Query object. */ public function set_ep_suggestion( $enabled, $query ): bool { if ( $query->is_search() && ! empty( $query->query_vars['s'] ) ) { $query->set( 'ep_suggestion', true ); } return $enabled; } /** * Returns requirements status of feature * * Requires the search feature to be activated */ public function requirements_status(): FeatureRequirementsStatus { return new FeatureRequirementsStatus( 1, null, $this ); } /** * Returns the list of other suggestions * * @param WP_Query $query WP_Query object * @return string|false */ protected function get_alternatives_terms( $query ) { global $wp_query; if ( ! $query && $wp_query->is_main_query() && $wp_query->is_search() ) { $query = $wp_query; } if ( ! is_a( $query, '\WP_Query' ) ) { return false; } $settings = $this->get_settings(); // If there are posts, we don't need to show the list of suggestions. if ( 'list' !== $settings['search_behavior'] || $query->found_posts ) { return false; } $options = $query->suggested_terms['options'] ?? []; array_shift( $options ); if ( empty( $options ) ) { return ''; } $html = '