PluginProbe
ElasticPress / 4.4.0
ElasticPress v4.4.0
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.4.0, at includes/classes/Feature/Search/Search.php

805 lines 23.7 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_action( 'pre_get_posts', [ $this, 'exclude_posts_from_search' ] );
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 }
114
115
116 /**
117 * Enqueue styles for highlighting.
118 */
119 public function enqueue_scripts() {
120 $settings = $this->get_settings();
121
122 if ( ! $settings ) {
123 $settings = [];
124 }
125
126 $settings = wp_parse_args( $settings, $this->default_settings );
127
128 if ( true !== $settings['highlight_enabled'] ) {
129 return;
130 }
131
132 wp_enqueue_style(
133 'searchterm-highlighting',
134 EP_URL . 'dist/css/highlighting-styles.css',
135 Utils\get_asset_info( 'highlighting-styles', 'dependencies' ),
136 Utils\get_asset_info( 'highlighting-styles', 'version' )
137 );
138 }
139
140 /**
141 * Set default fields to highlight, and outputs
142 * the tags on the front end.
143 *
144 * @param array $formatted_args ep_formatted_args array
145 * @param array $args WP_Query args
146 * @return array $formatted_args formatted args with search highlight tags
147 */
148 public function add_search_highlight_tags( $formatted_args, $args ) {
149
150 /**
151 * Fires before the highlighting clause is added to the Elasticsearch query
152 *
153 * @since 3.5.1
154 * @hook ep_highlighting_pre_add_highlight
155 * @param {array} $formatted_args ep_formatted_args array
156 * @param {string} $args WP_Query args
157 */
158 do_action( 'ep_highlighting_pre_add_highlight', $formatted_args, $args );
159
160 // get current config
161 $settings = $this->get_settings();
162
163 if ( ! $settings ) {
164 $settings = [];
165 }
166
167 $settings = wp_parse_args( $settings, $this->default_settings );
168
169 if ( true !== $settings['highlight_enabled'] ) {
170 return $formatted_args;
171 }
172
173 if ( empty( $args['s'] ) ) {
174 return $formatted_args;
175 }
176
177 /**
178 * Filter whether to add the `highlight` clause in the query or not.
179 *
180 * @since 3.5.6
181 * @hook ep_highlight_should_add_clause
182 * @param {bool} $add_highlight_clause True means the clause should be added.
183 * @param {array} $formatted_args ep_formatted_args array
184 * @param {array} $args WP query args
185 * @return {bool} New $add_highlight_clause value
186 */
187 $add_highlight_clause = apply_filters(
188 'ep_highlight_should_add_clause',
189 Utils\is_integrated_request( 'highlighting', [ 'public' ] ),
190 $formatted_args,
191 $args
192 );
193
194 if ( ! $add_highlight_clause ) {
195 return $formatted_args;
196 }
197
198 /**
199 * Filter the fields that should be highlighted.
200 *
201 * @since 3.5.1
202 * @hook ep_highlighting_fields
203 * @param {array} $fields Highlighting fields
204 * @param {array} $formatted_args array
205 * @param {array} $args WP_Query args
206 * @return {array} New Highlighting fields
207 */
208 $fields_to_highlight = apply_filters(
209 'ep_highlighting_fields',
210 [ 'post_title', 'post_content' ],
211 $formatted_args,
212 $args
213 );
214
215 // define the tag to use
216 $current_tag = $settings['highlight_tag'];
217
218 /**
219 * Filter the tag that wraps the search highlighted term
220 *
221 * @since 3.5
222 * @hook ep_highlighting_tag
223 * @param {string} $current_tag Highlighting tag
224 * @return {string} New highlighting tag
225 */
226 $highlight_tag = apply_filters( 'ep_highlighting_tag', $current_tag );
227
228 /**
229 * Filter class applied to search highlight tags
230 *
231 * @since 3.5
232 * @hook ep_highlighting_class
233 * @param {string} $class Highlighting class
234 * @return {string} New highlighting class
235 */
236 $highlight_class = apply_filters( 'ep_highlighting_class', 'ep-highlight' );
237
238 // tags
239 $opening_tag = '<' . $highlight_tag . " class='" . $highlight_class . "'>";
240 $closing_tag = '</' . $highlight_tag . '>';
241
242 foreach ( $fields_to_highlight as $field ) {
243 $formatted_args['highlight']['fields'][ $field ] = [
244 'pre_tags' => [ $opening_tag ],
245 'post_tags' => [ $closing_tag ],
246 'type' => 'plain',
247 'number_of_fragments' => 0,
248 ];
249 }
250
251 return $formatted_args;
252 }
253
254 /**
255 * Called by ep_highlighting_pre_add_highlight action.
256 *
257 * Replaces the default excerpt with the custom excerpt, allowing
258 * for the selected tag to be displayed in it.
259 */
260 public function allow_excerpt_html() {
261 if ( ! Utils\is_integrated_request( 'highlighting', [ 'public' ] ) ) {
262 return;
263 }
264
265 $settings = $this->get_settings();
266
267 if ( ! $settings ) {
268 $settings = [];
269 }
270
271 $settings = wp_parse_args( $settings, $this->default_settings );
272
273 if ( ! empty( $settings['highlight_excerpt'] ) && true === $settings['highlight_excerpt'] ) {
274 remove_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
275 add_filter( 'get_the_excerpt', [ $this, 'ep_highlight_excerpt' ], 10, 2 );
276 add_filter( 'ep_highlighting_fields', [ $this, 'ep_highlight_add_excerpt_field' ] );
277 }
278 }
279
280 /**
281 * Called by allow_excerpt_html
282 * logic for the excerpt filter allowing the currently selected tag.
283 *
284 * @param string $text excerpt string
285 * @param WP_Post $post Post Object
286 *
287 * @return string $text the new excerpt
288 */
289 public function ep_highlight_excerpt( $text, $post ) {
290
291 $settings = $this->get_settings();
292
293 if ( ! $settings ) {
294 $settings = [];
295 }
296
297 $settings = wp_parse_args( $settings, $this->default_settings );
298
299 // reproduces wp_trim_excerpt filter, preserving the excerpt_more and excerpt_length filters
300 if ( '' === $text ) {
301 $text = get_the_content( '', false, $post );
302 $text = apply_filters( 'the_content', $text );
303 $text = str_replace( '\]\]\>', ']]&gt;', $text );
304 $text = strip_tags( $text, '<' . esc_html( $settings['highlight_tag'] ) . '>' );
305
306 // use the defined length, if already applied...
307 $excerpt_length = apply_filters( 'excerpt_length', 55 );
308
309 // use defined excerpt_more filter if it is used
310 $excerpt_more = apply_filters( 'excerpt_more', $text );
311
312 $excerpt_more = $excerpt_more !== $text ? $excerpt_more : '[&hellip;]';
313
314 $words = explode( ' ', $text, $excerpt_length + 1 );
315 if ( count( $words ) > $excerpt_length ) {
316 array_pop( $words );
317 array_push( $words, $excerpt_more );
318 $text = implode( ' ', $words );
319 }
320 }
321
322 return $text;
323 }
324
325 /**
326 * Add `post_content` to the list of fields to highlight.
327 *
328 * @since 3.5.1
329 * @param array $fields_to_highlight The list of fields to highlight.
330 * @return array
331 */
332 public function ep_highlight_add_excerpt_field( $fields_to_highlight ) {
333 $fields_to_highlight[] = 'post_excerpt';
334 return $fields_to_highlight;
335 }
336
337 /**
338 * Helper filter to check if the tag is allowed.
339 *
340 * @param string $tag - html tag
341 * @return string
342 */
343 public function get_highlighting_tag( $tag ) {
344 if ( ! in_array( $tag, self::$default_highlight_tags, true ) ) {
345 $tag = 'mark';
346 }
347
348 return $tag;
349 }
350
351 /**
352 * Sanitizes our highlighting settings.
353 *
354 * @param array $settings Array of current settings
355 * @return mixed
356 */
357 public function sanitize_highlighting_settings( $settings ) {
358 if ( ! empty( $settings['search']['highlight_excerpt'] ) ) {
359 $settings['search']['highlight_excerpt'] = (bool) $settings['search']['highlight_excerpt'];
360 }
361
362 if ( ! empty( $settings['search']['highlight_enabled'] ) ) {
363 $settings['search']['highlight_enabled'] = (bool) $settings['search']['highlight_enabled'];
364 }
365
366 return $settings;
367 }
368
369 /**
370 * Returns searchable post types for the current site
371 *
372 * @since 1.9
373 * @return mixed|void
374 */
375 public function get_searchable_post_types() {
376 $post_types = get_post_types( array( 'exclude_from_search' => false ) );
377
378 /**
379 * Don't search attachments by default
380 *
381 * @since 3.0
382 */
383 unset( $post_types['attachment'] );
384
385 /**
386 * Filter searchable post types
387 *
388 * @hook ep_searchable_post_types
389 * @param {array} $post_types Post types
390 * @return {array} New post types
391 */
392 return apply_filters( 'ep_searchable_post_types', $post_types );
393 }
394
395 /**
396 * Make sure we don't search for "any" on a search query
397 *
398 * @param string $post_type Post type
399 * @param WP_Query $query WP Query
400 * @return string|array
401 */
402 public function filter_query_post_type_for_search( $post_type, $query ) {
403 if ( 'any' === $post_type && $query->is_search() ) {
404 $searchable_post_types = $this->get_searchable_post_types();
405
406 // If we have no searchable post types, there's no point going any further
407 if ( empty( $searchable_post_types ) ) {
408
409 // Have to return something or it improperly calculates the found_posts
410 return false;
411 }
412
413 // Conform the post types array to an acceptable format for ES
414 $post_types = [];
415
416 foreach ( $searchable_post_types as $type ) {
417 $post_types[] = $type;
418 }
419
420 // These are now the only post types we will search
421 $post_type = $post_types;
422 }
423
424 return $post_type;
425 }
426
427 /**
428 * Returns true/false if decaying is/isn't enabled
429 *
430 * @return bool
431 */
432 public function is_decaying_enabled() {
433 $settings = $this->get_settings();
434
435 $settings = wp_parse_args(
436 $settings,
437 [
438 'decaying_enabled' => true,
439 ]
440 );
441
442 return (bool) $settings['decaying_enabled'];
443 }
444
445 /**
446 * Weight more recent content in searches
447 *
448 * @param array $formatted_args Formatted ES args
449 * @param array $args WP_Query args
450 * @since 2.1
451 * @return array
452 */
453 public function weight_recent( $formatted_args, $args ) {
454 if ( empty( $args['s'] ) ) {
455 return $formatted_args;
456 }
457 if ( ! $this->is_decaying_enabled() ) {
458 return $formatted_args;
459 }
460 /**
461 * Filter search date weighting scale
462 *
463 * @hook epwr_decay_function
464 * @param {string} $decay_function Current decay function
465 * @param {array} $formatted_args Formatted Elasticsearch arguments
466 * @param {array} $args WP_Query arguments
467 * @return {string} New decay function
468 */
469 $decay_function = apply_filters( 'epwr_decay_function', 'exp', $formatted_args, $args );
470 /**
471 * Filter search date weighting field
472 *
473 * @hook epwr_decay_field
474 * @param {string} $field Current decay field
475 * @param {array} $formatted_args Formatted Elasticsearch arguments
476 * @param {array} $args WP_Query arguments
477 * @return {string} New decay field
478 * @since 4.3.0
479 */
480 $field = apply_filters( 'epwr_decay_field', 'post_date_gmt', $formatted_args, $args );
481 $date_score = array(
482 'function_score' => array(
483 'query' => $formatted_args['query'],
484 'functions' => array(
485 array(
486 $decay_function => array(
487 $field => array(
488 /**
489 * Filter search date weighting scale
490 *
491 * @hook epwr_scale
492 * @param {string} $scale Current scale
493 * @param {array} $formatted_args Formatted Elasticsearch arguments
494 * @param {array} $args WP_Query arguments
495 * @return {string} New scale
496 */
497 'scale' => apply_filters( 'epwr_scale', '14d', $formatted_args, $args ),
498 /**
499 * Filter search date weighting decay
500 *
501 * @hook epwr_decay
502 * @param {float} $decay Current decay
503 * @param {array} $formatted_args Formatted Elasticsearch arguments
504 * @param {array} $args WP_Query arguments
505 * @return {float} New decay
506 */
507 'decay' => apply_filters( 'epwr_decay', 0.25, $formatted_args, $args ),
508 /**
509 * Filter search date weighting offset
510 *
511 * @hook epwr_offset
512 * @param {string} $offset Current offset
513 * @param {array} $formatted_args Formatted Elasticsearch arguments
514 * @param {array} $args WP_Query arguments
515 * @return {string} New offset
516 */
517 'offset' => apply_filters( 'epwr_offset', '7d', $formatted_args, $args ),
518 ),
519 ),
520 ),
521 array(
522 /**
523 * Filter search date weight
524 *
525 * @since 3.5.6
526 * @hook epwr_weight
527 * @param {float} $weight Current weight
528 * @param {array} $formatted_args Formatted Elasticsearch arguments
529 * @param {array} $args WP_Query arguments
530 * @return {float} New weight
531 */
532 'weight' => apply_filters( 'epwr_weight', 0.001, $formatted_args, $args ),
533 ),
534 ),
535 /**
536 * Filter search date weighting score mode
537 *
538 * @hook epwr_score_mode
539 * @param {string} $score_mode Current score mode
540 * @param {array} $formatted_args Formatted Elasticsearch arguments
541 * @param {array} $args WP_Query arguments
542 * @return {string} New score mode
543 */
544 'score_mode' => apply_filters( 'epwr_score_mode', 'sum', $formatted_args, $args ),
545 /**
546 * Filter search date weighting boost mode
547 *
548 * @hook epwr_boost_mode
549 * @param {string} $boost_mode Current boost mode
550 * @param {array} $formatted_args Formatted Elasticsearch arguments
551 * @param {array} $args WP_Query arguments
552 * @return {string} New boost mode
553 */
554 'boost_mode' => apply_filters( 'epwr_boost_mode', 'multiply', $formatted_args, $args ),
555 ),
556 );
557
558 $formatted_args['query'] = $date_score;
559
560 return $formatted_args;
561 }
562
563 /**
564 * Output feature box long text
565 *
566 * @since 3.0
567 */
568 public function output_feature_box_long() {
569 ?>
570 <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>
571
572 <?php
573 }
574
575 /**
576 * Enable integration on search queries
577 *
578 * @param bool $enabled Original enabled value
579 * @param WP_Query $query WP Query
580 * @since 2.1
581 * @return bool
582 */
583 public function integrate_search_queries( $enabled, $query ) {
584 if ( ! Utils\is_integrated_request( $this->slug ) ) {
585 return false;
586 }
587
588 if ( ! is_a( $query, 'WP_Query' ) ) {
589 return $enabled;
590 }
591
592 if ( isset( $query->query_vars['ep_integrate'] ) && ! filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN ) ) {
593 return false;
594 }
595
596 if ( method_exists( $query, 'is_search' ) && $query->is_search() && ! empty( $query->query_vars['s'] ) ) {
597 $enabled = true;
598 }
599
600 /**
601 * Filter whether to enable integration on search queries or not.
602 *
603 * @hook ep_integrate_search_queries
604 * @since 4.2.0
605 * @param {bool} $enabled Original enabled value
606 * @param {WP_Query} $query WP_Query
607 * @return {bool} New $enabled value
608 */
609 return apply_filters( 'ep_integrate_search_queries', $enabled, $query );
610 }
611
612 /**
613 * Display decaying settings on dashboard.
614 *
615 * @since 2.4
616 */
617 public function output_feature_box_settings() {
618 $settings = $this->get_settings();
619
620 if ( ! $settings ) {
621 $settings = [];
622 }
623
624 $settings = wp_parse_args( $settings, $this->default_settings );
625
626 ?>
627 <div class="field">
628 <div class="field-name status"><?php esc_html_e( 'Weight results by date', 'elasticpress' ); ?></div>
629 <div class="input-wrap">
630 <label><input name="settings[decaying_enabled]" type="radio" <?php checked( (bool) $settings['decaying_enabled'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
631 <label><input name="settings[decaying_enabled]" type="radio" <?php checked( ! (bool) $settings['decaying_enabled'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
632 </div>
633 </div>
634 <div class="field">
635 <div class="field-name status"><?php esc_html_e( 'Highlighting status', 'elasticpress' ); ?></div>
636 <div class="input-wrap">
637 <label><input name="settings[highlight_enabled]" type="radio" <?php checked( (bool) $settings['highlight_enabled'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
638 <label><input name="settings[highlight_enabled]" type="radio" <?php checked( ! (bool) $settings['highlight_enabled'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
639 <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>
640 </div>
641 </div>
642 <div class="field">
643 <label for="highlight-tag" class="field-name status"><?php echo esc_html_e( 'Highlight tag ', 'elasticpress' ); ?></label>
644 <div class="input-wrap">
645 <select id="highlight-tag" name="settings[highlight_tag]">
646 <?php
647 foreach ( self::$default_highlight_tags as $option ) :
648 echo '<option value="' . esc_attr( $option ) . '" ' . selected( $option, $settings['highlight_tag'] ) . '>' . esc_html( $option ) . '</option>';
649 endforeach;
650 ?>
651 </select>
652 </div>
653 </div>
654
655 <div class="field">
656 <div class="field-name status"><?php esc_html_e( 'Excerpt highlighting', 'elasticpress' ); ?></div>
657 <div class="input-wrap">
658 <label><input name="settings[highlight_excerpt]" type="radio" <?php checked( (bool) $settings['highlight_excerpt'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
659 <label><input name="settings[highlight_excerpt]" type="radio" <?php checked( ! (bool) $settings['highlight_excerpt'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
660 <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>
661 </div>
662 </div>
663
664 <?php if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) : ?>
665 <br class="clear">
666 <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>
667 <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>
668 <?php endif; ?>
669
670 <?php
671 }
672
673 /**
674 * Registers post meta for exclude from search feature.
675 */
676 public function register_meta() {
677 register_post_meta(
678 '',
679 'ep_exclude_from_search',
680 [
681 'show_in_rest' => true,
682 'single' => true,
683 'type' => 'boolean',
684 ]
685 );
686 }
687
688 /**
689 * Enqueue block editor assets.
690 */
691 public function enqueue_block_editor_assets() {
692 global $post;
693
694 if ( ! $post instanceof \WP_Post ) {
695 return;
696 }
697
698 wp_enqueue_script(
699 'ep-search-editor',
700 EP_URL . '/dist/js/search-editor-script.js',
701 Utils\get_asset_info( 'search-editor-script', 'dependencies' ),
702 Utils\get_asset_info( 'search-editor-script', 'version' ),
703 true
704 );
705 }
706
707 /**
708 * Exclude posts based on ep_exclude_from_search post meta.
709 *
710 * @param WP_Query $query WP Query
711 */
712 public function exclude_posts_from_search( $query ) {
713 $bypass_exclusion_from_search = is_admin() || ! $query->is_search();
714 /**
715 * Filter whether the exclusion from the "exclude from search" checkbox should be applied
716 *
717 * @since 4.4.0
718 * @hook ep_bypass_exclusion_from_search
719 * @param {bool} $bypass_exclusion_from_search True means all posts will be returned
720 * @param {WP_Query} $query WP Query
721 * @return {bool} New $bypass_exclusion_from_search value
722 */
723 if ( apply_filters( 'ep_bypass_exclusion_from_search', $bypass_exclusion_from_search, $query ) ) {
724 return;
725 }
726
727 // Get any meta query that's being added before.
728 $meta_query = (array) $query->get( 'meta_query' );
729
730 $exclude_from_search_query = [
731 'relation' => 'or',
732 [
733 'key' => 'ep_exclude_from_search',
734 'compare' => 'NOT EXISTS',
735 ],
736 [
737 'key' => 'ep_exclude_from_search',
738 'value' => '1',
739 'compare' => '!=',
740 ],
741 ];
742
743 /**
744 * If the current meta query only has an `OR` clause, wrap it with an `AND`
745 * so the criteria here is not made "optional".
746 */
747 if ( empty( $meta_query ) || empty( $meta_query['relation'] ) || 'and' === strtolower( $meta_query['relation'] ) ) {
748 $meta_query[] = $exclude_from_search_query;
749 } else {
750 $meta_query = [
751 'relation' => 'and',
752 $exclude_from_search_query,
753 $meta_query,
754 ];
755 }
756
757 $query->set( 'meta_query', $meta_query );
758 }
759
760 /**
761 * Outputs the checkbox to exclude a post from search.
762 *
763 * @param WP_POST $post Post object.
764 */
765 public function output_exclude_from_search_setting( $post ) {
766
767 $searchable_post_types = $this->get_searchable_post_types();
768 if ( ! in_array( $post->post_type, $searchable_post_types, true ) ) {
769 return;
770 }
771 ?>
772 <div class="misc-pub-section">
773 <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 ) ); ?>>
774 <label for="ep_exclude_from_search"><?php esc_html_e( 'Exclude from Search', 'elasticpress' ); ?></label>
775 <?php wp_nonce_field( 'save-exclude-from-search', 'ep-exclude-from-search-nonce' ); ?>
776 </div>
777 <?php
778 }
779
780 /**
781 * Saves exclude from search meta.
782 *
783 * @param int $post_id The post ID.
784 * @param WP_Post $post Post object.
785 */
786 public function save_exclude_from_search_meta( $post_id, $post ) {
787
788 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
789 return;
790 }
791
792 if ( ! isset( $_POST['ep-exclude-from-search-nonce'] ) || ! wp_verify_nonce( $_POST['ep-exclude-from-search-nonce'], 'save-exclude-from-search' ) ) {
793 return;
794 }
795
796 if ( ! current_user_can( 'edit_post', $post_id ) ) {
797 return;
798 }
799
800 $exclude_from_search = isset( $_POST['ep_exclude_from_search'] ) ? true : false;
801
802 update_post_meta( $post_id, 'ep_exclude_from_search', $exclude_from_search );
803 }
804 }
805