PluginProbe
ElasticPress / 4.5.2
ElasticPress v4.5.2
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Feature / Search / Search.php

Search.php in ElasticPress 4.5.2, at includes/classes/Feature/Search/Search.php

826 lines 24.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Search feature
4 *
5 * @since 1.9
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Feature\Search;
10
11 use ElasticPress\Feature as Feature;
12 use ElasticPress\Indexables as Indexables;
13 use ElasticPress\Utils as Utils;
14
15 /**
16 * Search feature class
17 */
18 class Search extends Feature {
19 /**
20 * Synonyms Class (Sub Feature)
21 *
22 * @var Synonyms
23 */
24 public $synonyms;
25
26 /**
27 * Weighting Class (Sub Feature)
28 *
29 * @var Weighting
30 */
31 public $weighting;
32
33 /**
34 * Highlighting tags
35 *
36 * @var array
37 */
38 public static $default_highlight_tags = [
39 'mark',
40 'span',
41 'strong',
42 'em',
43 'i',
44 ];
45
46 /**
47 * Initialize feature setting it's config
48 *
49 * @since 3.0
50 */
51 public function __construct() {
52 $this->slug = 'search';
53
54 $this->title = esc_html__( 'Post Search', 'elasticpress' );
55
56 $this->summary = __( 'Instantly find the content you’re looking for. The first time.', 'elasticpress' );
57
58 $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#post-search', 'elasticpress' );
59
60 $this->requires_install_reindex = false;
61
62 $this->default_settings = [
63 'decaying_enabled' => '1',
64 'synonyms_editor_mode' => 'simple',
65 'highlight_enabled' => '0',
66 'highlight_excerpt' => '0',
67 'highlight_tag' => 'mark',
68 ];
69
70 $this->available_during_installation = true;
71
72 parent::__construct();
73 }
74
75 /**
76 * We need to delay search setup up since it will fire after protected content and protected
77 * content filters into the search setup
78 *
79 * @since 2.2
80 */
81 public function setup() {
82 add_action( 'init', [ $this, 'search_setup' ] );
83 add_filter( 'ep_sanitize_feature_settings', [ $this, 'sanitize_highlighting_settings' ] );
84
85 // Set up weighting sub-module
86 $this->weighting = new Weighting();
87 $this->weighting->setup();
88
89 $this->synonyms = new Synonyms();
90 $this->synonyms->setup();
91 }
92
93 /**
94 * Setup feature on each page load
95 *
96 * @since 3.0
97 */
98 public function search_setup() {
99 add_filter( 'ep_elasticpress_enabled', [ $this, 'integrate_search_queries' ], 10, 2 );
100 add_filter( 'ep_formatted_args', [ $this, 'weight_recent' ], 11, 2 );
101 add_filter( 'ep_query_post_type', [ $this, 'filter_query_post_type_for_search' ], 10, 2 );
102
103 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
104 add_filter( 'ep_formatted_args', [ $this, 'add_search_highlight_tags' ], 10, 2 );
105 add_filter( 'ep_highlighting_tag', [ $this, 'get_highlighting_tag' ] );
106 add_action( 'ep_highlighting_pre_add_highlight', [ $this, 'allow_excerpt_html' ] );
107
108 add_action( 'init', [ $this, 'register_meta' ], 20 );
109 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] );
110 add_filter( 'ep_post_filters', [ $this, 'exclude_posts_from_search' ], 10, 3 );
111 add_action( 'post_submitbox_misc_actions', [ $this, 'output_exclude_from_search_setting' ] );
112 add_action( 'edit_post', [ $this, 'save_exclude_from_search_meta' ], 10, 2 );
113 add_filter( 'ep_skip_query_integration', [ $this, 'skip_query_integration' ], 10, 2 );
114 }
115
116
117 /**
118 * Enqueue styles for highlighting.
119 */
120 public function enqueue_scripts() {
121 $settings = $this->get_settings();
122
123 if ( ! $settings ) {
124 $settings = [];
125 }
126
127 $settings = wp_parse_args( $settings, $this->default_settings );
128
129 if ( true !== $settings['highlight_enabled'] ) {
130 return;
131 }
132
133 wp_enqueue_style(
134 'searchterm-highlighting',
135 EP_URL . 'dist/css/highlighting-styles.css',
136 Utils\get_asset_info( 'highlighting-styles', 'dependencies' ),
137 Utils\get_asset_info( 'highlighting-styles', 'version' )
138 );
139 }
140
141 /**
142 * Set default fields to highlight, and outputs
143 * the tags on the front end.
144 *
145 * @param array $formatted_args ep_formatted_args array
146 * @param array $args WP_Query args
147 * @return array $formatted_args formatted args with search highlight tags
148 */
149 public function add_search_highlight_tags( $formatted_args, $args ) {
150
151 /**
152 * Fires before the highlighting clause is added to the Elasticsearch query
153 *
154 * @since 3.5.1
155 * @hook ep_highlighting_pre_add_highlight
156 * @param {array} $formatted_args ep_formatted_args array
157 * @param {string} $args WP_Query args
158 */
159 do_action( 'ep_highlighting_pre_add_highlight', $formatted_args, $args );
160
161 // get current config
162 $settings = $this->get_settings();
163
164 if ( ! $settings ) {
165 $settings = [];
166 }
167
168 $settings = wp_parse_args( $settings, $this->default_settings );
169
170 if ( true !== $settings['highlight_enabled'] ) {
171 return $formatted_args;
172 }
173
174 if ( empty( $args['s'] ) ) {
175 return $formatted_args;
176 }
177
178 /**
179 * Filter whether to add the `highlight` clause in the query or not.
180 *
181 * @since 3.5.6
182 * @hook ep_highlight_should_add_clause
183 * @param {bool} $add_highlight_clause True means the clause should be added.
184 * @param {array} $formatted_args ep_formatted_args array
185 * @param {array} $args WP query args
186 * @return {bool} New $add_highlight_clause value
187 */
188 $add_highlight_clause = apply_filters(
189 'ep_highlight_should_add_clause',
190 Utils\is_integrated_request( 'highlighting', [ 'public' ] ),
191 $formatted_args,
192 $args
193 );
194
195 if ( ! $add_highlight_clause ) {
196 return $formatted_args;
197 }
198
199 /**
200 * Filter the fields that should be highlighted.
201 *
202 * @since 3.5.1
203 * @hook ep_highlighting_fields
204 * @param {array} $fields Highlighting fields
205 * @param {array} $formatted_args array
206 * @param {array} $args WP_Query args
207 * @return {array} New Highlighting fields
208 */
209 $fields_to_highlight = apply_filters(
210 'ep_highlighting_fields',
211 [ 'post_title', 'post_content' ],
212 $formatted_args,
213 $args
214 );
215
216 // define the tag to use
217 $current_tag = $settings['highlight_tag'];
218
219 /**
220 * Filter the tag that wraps the search highlighted term
221 *
222 * @since 3.5
223 * @hook ep_highlighting_tag
224 * @param {string} $current_tag Highlighting tag
225 * @return {string} New highlighting tag
226 */
227 $highlight_tag = apply_filters( 'ep_highlighting_tag', $current_tag );
228
229 /**
230 * Filter class applied to search highlight tags
231 *
232 * @since 3.5
233 * @hook ep_highlighting_class
234 * @param {string} $class Highlighting class
235 * @return {string} New highlighting class
236 */
237 $highlight_class = apply_filters( 'ep_highlighting_class', 'ep-highlight' );
238
239 // tags
240 $opening_tag = '<' . $highlight_tag . " class='" . $highlight_class . "'>";
241 $closing_tag = '</' . $highlight_tag . '>';
242
243 foreach ( $fields_to_highlight as $field ) {
244 $formatted_args['highlight']['fields'][ $field ] = [
245 'pre_tags' => [ $opening_tag ],
246 'post_tags' => [ $closing_tag ],
247 'type' => 'plain',
248 'number_of_fragments' => 0,
249 ];
250 }
251
252 return $formatted_args;
253 }
254
255 /**
256 * Called by ep_highlighting_pre_add_highlight action.
257 *
258 * Replaces the default excerpt with the custom excerpt, allowing
259 * for the selected tag to be displayed in it.
260 */
261 public function allow_excerpt_html() {
262 if ( ! Utils\is_integrated_request( 'highlighting', [ 'public' ] ) ) {
263 return;
264 }
265
266 $settings = $this->get_settings();
267
268 if ( ! $settings ) {
269 $settings = [];
270 }
271
272 $settings = wp_parse_args( $settings, $this->default_settings );
273
274 if ( ! empty( $settings['highlight_excerpt'] ) && true === $settings['highlight_excerpt'] ) {
275 remove_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
276 add_filter( 'get_the_excerpt', [ $this, 'ep_highlight_excerpt' ], 10, 2 );
277 add_filter( 'ep_highlighting_fields', [ $this, 'ep_highlight_add_excerpt_field' ] );
278 }
279 }
280
281 /**
282 * Called by allow_excerpt_html
283 * logic for the excerpt filter allowing the currently selected tag.
284 *
285 * @param string $text excerpt string
286 * @param WP_Post $post Post Object
287 *
288 * @return string $text the new excerpt
289 */
290 public function ep_highlight_excerpt( $text, $post ) {
291
292 $settings = $this->get_settings();
293
294 if ( ! $settings ) {
295 $settings = [];
296 }
297
298 $settings = wp_parse_args( $settings, $this->default_settings );
299
300 // reproduces wp_trim_excerpt filter, preserving the excerpt_more and excerpt_length filters
301 if ( '' === $text ) {
302 $text = get_the_content( '', false, $post );
303 $text = apply_filters( 'the_content', $text );
304 $text = str_replace( '\]\]\>', ']]&gt;', $text );
305 $text = strip_tags( $text, '<' . esc_html( $settings['highlight_tag'] ) . '>' );
306
307 // use the defined length, if already applied...
308 $excerpt_length = apply_filters( 'excerpt_length', 55 );
309
310 // use defined excerpt_more filter if it is used
311 $excerpt_more = apply_filters( 'excerpt_more', $text );
312
313 $excerpt_more = $excerpt_more !== $text ? $excerpt_more : '[&hellip;]';
314
315 $words = explode( ' ', $text, $excerpt_length + 1 );
316 if ( count( $words ) > $excerpt_length ) {
317 array_pop( $words );
318 array_push( $words, $excerpt_more );
319 $text = implode( ' ', $words );
320 }
321 }
322
323 return $text;
324 }
325
326 /**
327 * Add `post_content` to the list of fields to highlight.
328 *
329 * @since 3.5.1
330 * @param array $fields_to_highlight The list of fields to highlight.
331 * @return array
332 */
333 public function ep_highlight_add_excerpt_field( $fields_to_highlight ) {
334 $fields_to_highlight[] = 'post_excerpt';
335 return $fields_to_highlight;
336 }
337
338 /**
339 * Helper filter to check if the tag is allowed.
340 *
341 * @param string $tag - html tag
342 * @return string
343 */
344 public function get_highlighting_tag( $tag ) {
345 if ( ! in_array( $tag, self::$default_highlight_tags, true ) ) {
346 $tag = 'mark';
347 }
348
349 return $tag;
350 }
351
352 /**
353 * Sanitizes our highlighting settings.
354 *
355 * @param array $settings Array of current settings
356 * @return mixed
357 */
358 public function sanitize_highlighting_settings( $settings ) {
359 if ( ! empty( $settings['search']['highlight_excerpt'] ) ) {
360 $settings['search']['highlight_excerpt'] = (bool) $settings['search']['highlight_excerpt'];
361 }
362
363 if ( ! empty( $settings['search']['highlight_enabled'] ) ) {
364 $settings['search']['highlight_enabled'] = (bool) $settings['search']['highlight_enabled'];
365 }
366
367 return $settings;
368 }
369
370 /**
371 * Returns searchable post types for the current site
372 *
373 * @since 1.9
374 * @return mixed|void
375 */
376 public function get_searchable_post_types() {
377 $post_types = get_post_types( array( 'exclude_from_search' => false ) );
378
379 /**
380 * Don't search attachments by default
381 *
382 * @since 3.0
383 */
384 unset( $post_types['attachment'] );
385
386 /**
387 * Filter searchable post types
388 *
389 * @hook ep_searchable_post_types
390 * @param {array} $post_types Post types
391 * @return {array} New post types
392 */
393 return apply_filters( 'ep_searchable_post_types', $post_types );
394 }
395
396 /**
397 * Make sure we don't search for "any" on a search query
398 *
399 * @param string $post_type Post type
400 * @param WP_Query $query WP Query
401 * @return string|array
402 */
403 public function filter_query_post_type_for_search( $post_type, $query ) {
404 if ( 'any' === $post_type && $query->is_search() ) {
405 $searchable_post_types = $this->get_searchable_post_types();
406
407 // If we have no searchable post types, there's no point going any further
408 if ( empty( $searchable_post_types ) ) {
409
410 // Have to return something or it improperly calculates the found_posts
411 return false;
412 }
413
414 // Conform the post types array to an acceptable format for ES
415 $post_types = [];
416
417 foreach ( $searchable_post_types as $type ) {
418 $post_types[] = $type;
419 }
420
421 // These are now the only post types we will search
422 $post_type = $post_types;
423 }
424
425 return $post_type;
426 }
427
428 /**
429 * Returns true/false if decaying is/isn't enabled
430 *
431 * @return bool
432 */
433 public function is_decaying_enabled() {
434 $settings = $this->get_settings();
435
436 $settings = wp_parse_args(
437 $settings,
438 [
439 'decaying_enabled' => true,
440 ]
441 );
442
443 return (bool) $settings['decaying_enabled'];
444 }
445
446 /**
447 * Weight more recent content in searches
448 *
449 * @param array $formatted_args Formatted ES args
450 * @param array $args WP_Query args
451 * @since 2.1
452 * @return array
453 */
454 public function weight_recent( $formatted_args, $args ) {
455 if ( empty( $args['s'] ) ) {
456 return $formatted_args;
457 }
458 if ( ! $this->is_decaying_enabled() ) {
459 return $formatted_args;
460 }
461 /**
462 * Filter search date weighting scale
463 *
464 * @hook epwr_decay_function
465 * @param {string} $decay_function Current decay function
466 * @param {array} $formatted_args Formatted Elasticsearch arguments
467 * @param {array} $args WP_Query arguments
468 * @return {string} New decay function
469 */
470 $decay_function = apply_filters( 'epwr_decay_function', 'exp', $formatted_args, $args );
471 /**
472 * Filter search date weighting field
473 *
474 * @hook epwr_decay_field
475 * @param {string} $field Current decay field
476 * @param {array} $formatted_args Formatted Elasticsearch arguments
477 * @param {array} $args WP_Query arguments
478 * @return {string} New decay field
479 * @since 4.3.0
480 */
481 $field = apply_filters( 'epwr_decay_field', 'post_date_gmt', $formatted_args, $args );
482 $date_score = array(
483 'function_score' => array(
484 'query' => $formatted_args['query'],
485 'functions' => array(
486 array(
487 $decay_function => array(
488 $field => array(
489 /**
490 * Filter search date weighting scale
491 *
492 * @hook epwr_scale
493 * @param {string} $scale Current scale
494 * @param {array} $formatted_args Formatted Elasticsearch arguments
495 * @param {array} $args WP_Query arguments
496 * @return {string} New scale
497 */
498 'scale' => apply_filters( 'epwr_scale', '14d', $formatted_args, $args ),
499 /**
500 * Filter search date weighting decay
501 *
502 * @hook epwr_decay
503 * @param {float} $decay Current decay
504 * @param {array} $formatted_args Formatted Elasticsearch arguments
505 * @param {array} $args WP_Query arguments
506 * @return {float} New decay
507 */
508 'decay' => apply_filters( 'epwr_decay', 0.25, $formatted_args, $args ),
509 /**
510 * Filter search date weighting offset
511 *
512 * @hook epwr_offset
513 * @param {string} $offset Current offset
514 * @param {array} $formatted_args Formatted Elasticsearch arguments
515 * @param {array} $args WP_Query arguments
516 * @return {string} New offset
517 */
518 'offset' => apply_filters( 'epwr_offset', '7d', $formatted_args, $args ),
519 ),
520 ),
521 ),
522 array(
523 /**
524 * Filter search date weight
525 *
526 * @since 3.5.6
527 * @hook epwr_weight
528 * @param {float} $weight Current weight
529 * @param {array} $formatted_args Formatted Elasticsearch arguments
530 * @param {array} $args WP_Query arguments
531 * @return {float} New weight
532 */
533 'weight' => apply_filters( 'epwr_weight', 0.001, $formatted_args, $args ),
534 ),
535 ),
536 /**
537 * Filter search date weighting score mode
538 *
539 * @hook epwr_score_mode
540 * @param {string} $score_mode Current score mode
541 * @param {array} $formatted_args Formatted Elasticsearch arguments
542 * @param {array} $args WP_Query arguments
543 * @return {string} New score mode
544 */
545 'score_mode' => apply_filters( 'epwr_score_mode', 'sum', $formatted_args, $args ),
546 /**
547 * Filter search date weighting boost mode
548 *
549 * @hook epwr_boost_mode
550 * @param {string} $boost_mode Current boost mode
551 * @param {array} $formatted_args Formatted Elasticsearch arguments
552 * @param {array} $args WP_Query arguments
553 * @return {string} New boost mode
554 */
555 'boost_mode' => apply_filters( 'epwr_boost_mode', 'multiply', $formatted_args, $args ),
556 ),
557 );
558
559 $formatted_args['query'] = $date_score;
560
561 return $formatted_args;
562 }
563
564 /**
565 * Output feature box long text
566 *
567 * @since 3.0
568 */
569 public function output_feature_box_long() {
570 ?>
571 <p><?php esc_html_e( 'Overcome higher-end performance and functional limits posed by the traditional WordPress structured (SQL) database to deliver superior keyword search, instantly. ElasticPress indexes custom fields, tags, and other metadata to improve search results. Fuzzy matching accounts for misspellings and verb tenses.', 'elasticpress' ); ?></p>
572
573 <?php
574 }
575
576 /**
577 * Enable integration on search queries
578 *
579 * @param bool $enabled Original enabled value
580 * @param WP_Query $query WP Query
581 * @since 2.1
582 * @return bool
583 */
584 public function integrate_search_queries( $enabled, $query ) {
585 if ( ! Utils\is_integrated_request( $this->slug ) ) {
586 return false;
587 }
588
589 if ( ! is_a( $query, 'WP_Query' ) ) {
590 return $enabled;
591 }
592
593 if ( isset( $query->query_vars['ep_integrate'] ) && ! filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN ) ) {
594 return false;
595 }
596
597 if ( method_exists( $query, 'is_search' ) && $query->is_search() && ! empty( $query->query_vars['s'] ) ) {
598 $enabled = true;
599 }
600
601 /**
602 * Filter whether to enable integration on search queries or not.
603 *
604 * @hook ep_integrate_search_queries
605 * @since 4.2.0
606 * @param {bool} $enabled Original enabled value
607 * @param {WP_Query} $query WP_Query
608 * @return {bool} New $enabled value
609 */
610 return apply_filters( 'ep_integrate_search_queries', $enabled, $query );
611 }
612
613 /**
614 * Display decaying settings on dashboard.
615 *
616 * @since 2.4
617 */
618 public function output_feature_box_settings() {
619 $settings = $this->get_settings();
620
621 if ( ! $settings ) {
622 $settings = [];
623 }
624
625 $settings = wp_parse_args( $settings, $this->default_settings );
626
627 ?>
628 <div class="field">
629 <div class="field-name status"><?php esc_html_e( 'Weight results by date', 'elasticpress' ); ?></div>
630 <div class="input-wrap">
631 <label><input name="settings[decaying_enabled]" type="radio" <?php checked( (bool) $settings['decaying_enabled'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
632 <label><input name="settings[decaying_enabled]" type="radio" <?php checked( ! (bool) $settings['decaying_enabled'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
633 </div>
634 </div>
635 <div class="field">
636 <div class="field-name status"><?php esc_html_e( 'Highlighting status', 'elasticpress' ); ?></div>
637 <div class="input-wrap">
638 <label><input name="settings[highlight_enabled]" type="radio" <?php checked( (bool) $settings['highlight_enabled'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
639 <label><input name="settings[highlight_enabled]" type="radio" <?php checked( ! (bool) $settings['highlight_enabled'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
640 <p class="field-description"><?php esc_html_e( 'Wrap search terms in HTML tags in results for custom styling. The wrapping HTML tag comes with the "ep-highlight" class for easy styling.' ); ?></p>
641 </div>
642 </div>
643 <div class="field">
644 <label for="highlight-tag" class="field-name status"><?php echo esc_html_e( 'Highlight tag ', 'elasticpress' ); ?></label>
645 <div class="input-wrap">
646 <select id="highlight-tag" name="settings[highlight_tag]">
647 <?php
648 foreach ( self::$default_highlight_tags as $option ) :
649 echo '<option value="' . esc_attr( $option ) . '" ' . selected( $option, $settings['highlight_tag'] ) . '>' . esc_html( $option ) . '</option>';
650 endforeach;
651 ?>
652 </select>
653 </div>
654 </div>
655
656 <div class="field">
657 <div class="field-name status"><?php esc_html_e( 'Excerpt highlighting', 'elasticpress' ); ?></div>
658 <div class="input-wrap">
659 <label><input name="settings[highlight_excerpt]" type="radio" <?php checked( (bool) $settings['highlight_excerpt'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
660 <label><input name="settings[highlight_excerpt]" type="radio" <?php checked( ! (bool) $settings['highlight_excerpt'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
661 <p class="field-description"><?php esc_html_e( 'By default, WordPress strips HTML from content excerpts. Enable when using the_excerpt() to display search results. ', 'elasticpress' ); ?></p>
662 </div>
663 </div>
664
665 <?php if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) : ?>
666 <br class="clear">
667 <p><a href="<?php echo esc_url( admin_url( 'admin.php?page=elasticpress-weighting' ) ); ?>"><?php esc_html_e( 'Advanced fields and weighting settings', 'elasticpress' ); ?></a></p>
668 <p><a href="<?php echo esc_url( admin_url( 'admin.php?page=elasticpress-synonyms' ) ); ?>"><?php esc_html_e( 'Add synonyms to your post searches', 'elasticpress' ); ?></a></p>
669 <?php endif; ?>
670
671 <?php
672 }
673
674 /**
675 * Registers post meta for exclude from search feature.
676 */
677 public function register_meta() {
678 register_post_meta(
679 '',
680 'ep_exclude_from_search',
681 [
682 'show_in_rest' => true,
683 'single' => true,
684 'type' => 'boolean',
685 ]
686 );
687 }
688
689 /**
690 * Enqueue block editor assets.
691 */
692 public function enqueue_block_editor_assets() {
693 global $post;
694
695 if ( ! $post instanceof \WP_Post ) {
696 return;
697 }
698
699 wp_enqueue_script(
700 'ep-search-editor',
701 EP_URL . '/dist/js/search-editor-script.js',
702 Utils\get_asset_info( 'search-editor-script', 'dependencies' ),
703 Utils\get_asset_info( 'search-editor-script', 'version' ),
704 true
705 );
706 }
707
708 /**
709 * Exclude posts based on ep_exclude_from_search post meta.
710 *
711 * @param array $filters Filters to be applied to the query
712 * @param array $args WP Query args
713 * @param WP_Query $query WP Query object
714 */
715 public function exclude_posts_from_search( $filters, $args, $query ) {
716 $bypass_exclusion_from_search = is_admin() || ! $query->is_search();
717 /**
718 * Filter whether the exclusion from the "exclude from search" checkbox should be applied
719 *
720 * @since 4.4.0
721 * @hook ep_bypass_exclusion_from_search
722 * @param {bool} $bypass_exclusion_from_search True means all posts will be returned
723 * @param {WP_Query} $query WP Query
724 * @return {bool} New $bypass_exclusion_from_search value
725 */
726 if ( apply_filters( 'ep_bypass_exclusion_from_search', $bypass_exclusion_from_search, $query ) ) {
727 return $filters;
728 }
729
730 $filters[] = [
731 'bool' => [
732 'must_not' => [
733 [
734 'terms' => [
735 'meta.ep_exclude_from_search.raw' => [ '1' ],
736 ],
737 ],
738 ],
739 ],
740 ];
741
742 return $filters;
743 }
744
745 /**
746 * Outputs the checkbox to exclude a post from search.
747 *
748 * @param WP_POST $post Post object.
749 */
750 public function output_exclude_from_search_setting( $post ) {
751
752 $searchable_post_types = $this->get_searchable_post_types();
753 if ( ! in_array( $post->post_type, $searchable_post_types, true ) ) {
754 return;
755 }
756 ?>
757 <div class="misc-pub-section">
758 <input id="ep_exclude_from_search" name="ep_exclude_from_search" type="checkbox" value="1" <?php checked( get_post_meta( get_the_ID(), 'ep_exclude_from_search', true ) ); ?>>
759 <label for="ep_exclude_from_search"><?php esc_html_e( 'Exclude from search results', 'elasticpress' ); ?></label>
760 <p class="howto"><?php esc_html_e( 'Excludes this post from the results of your site\'s search form while ElasticPress is active.', 'elasticpress' ); ?></p>
761 <?php wp_nonce_field( 'save-exclude-from-search', 'ep-exclude-from-search-nonce' ); ?>
762 </div>
763 <?php
764 }
765
766 /**
767 * Saves exclude from search meta.
768 *
769 * @param int $post_id The post ID.
770 * @param WP_Post $post Post object.
771 */
772 public function save_exclude_from_search_meta( $post_id, $post ) {
773
774 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
775 return;
776 }
777
778 if ( ! isset( $_POST['ep-exclude-from-search-nonce'] ) || ! wp_verify_nonce( $_POST['ep-exclude-from-search-nonce'], 'save-exclude-from-search' ) ) {
779 return;
780 }
781
782 if ( ! current_user_can( 'edit_post', $post_id ) ) {
783 return;
784 }
785
786 $exclude_from_search = isset( $_POST['ep_exclude_from_search'] ) ? true : false;
787
788 update_post_meta( $post_id, 'ep_exclude_from_search', $exclude_from_search );
789 }
790
791 /**
792 * If WP_Query has unsupported orderby, skip ES query integration and use the WP query instead.
793 *
794 * @param bool $skip Whether to skip ES query integration
795 * @param \WP_Query $query WP_Query object
796 *
797 * @since 4.5
798 * @return bool
799 */
800 public function skip_query_integration( $skip, $query ) {
801 if ( ! $query instanceof \WP_Query ) {
802 return $skip;
803 }
804
805 $unsupported_orderby = [
806 'post__in',
807 'post_name__in',
808 'post_parent__in',
809 'parent',
810 ];
811
812 $orderby = is_string( $query->get( 'orderby' ) ) ? explode( ' ', $query->get( 'orderby' ) ) : $query->get( 'orderby', 'date' );
813
814 $parse_orderby = array();
815 foreach ( $orderby as $key => $value ) {
816 $parse_orderby[] = is_string( $key ) ? $key : $value;
817 }
818
819 if ( array_intersect( $parse_orderby, $unsupported_orderby ) ) {
820 return true;
821 }
822
823 return $skip;
824 }
825 }
826