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

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