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

834 lines 25.2 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' ] );
114 add_filter( 'ep_skip_query_integration', [ $this, 'skip_query_integration' ], 10, 2 );
115
116 add_action( 'attachment_submitbox_misc_actions', [ $this, 'output_exclude_from_search_setting' ], 15 );
117 add_action( 'edit_attachment', [ $this, 'save_exclude_from_search_meta' ] );
118 }
119
120
121 /**
122 * Enqueue styles for highlighting.
123 */
124 public function enqueue_scripts() {
125 $settings = $this->get_settings();
126
127 if ( true !== $settings['highlight_enabled'] ) {
128 return;
129 }
130
131 wp_enqueue_style(
132 'searchterm-highlighting',
133 EP_URL . 'dist/css/highlighting-styles.css',
134 Utils\get_asset_info( 'highlighting-styles', 'dependencies' ),
135 Utils\get_asset_info( 'highlighting-styles', 'version' )
136 );
137 }
138
139 /**
140 * Set default fields to highlight, and outputs
141 * the tags on the front end.
142 *
143 * @param array $formatted_args ep_formatted_args array
144 * @param array $args WP_Query args
145 * @return array $formatted_args formatted args with search highlight tags
146 */
147 public function add_search_highlight_tags( $formatted_args, $args ) {
148
149 /**
150 * Fires before the highlighting clause is added to the Elasticsearch query
151 *
152 * @since 3.5.1
153 * @hook ep_highlighting_pre_add_highlight
154 * @param {array} $formatted_args ep_formatted_args array
155 * @param {string} $args WP_Query args
156 */
157 do_action( 'ep_highlighting_pre_add_highlight', $formatted_args, $args );
158
159 // get current config
160 $settings = $this->get_settings();
161
162 if ( true !== $settings['highlight_enabled'] ) {
163 return $formatted_args;
164 }
165
166 if ( empty( $args['s'] ) ) {
167 return $formatted_args;
168 }
169
170 /**
171 * Filter whether to add the `highlight` clause in the query or not.
172 *
173 * @since 3.5.6
174 * @hook ep_highlight_should_add_clause
175 * @param {bool} $add_highlight_clause True means the clause should be added.
176 * @param {array} $formatted_args ep_formatted_args array
177 * @param {array} $args WP query args
178 * @return {bool} New $add_highlight_clause value
179 */
180 $add_highlight_clause = apply_filters(
181 'ep_highlight_should_add_clause',
182 Utils\is_integrated_request( 'highlighting', [ 'public' ] ),
183 $formatted_args,
184 $args
185 );
186
187 if ( ! $add_highlight_clause ) {
188 return $formatted_args;
189 }
190
191 /**
192 * Filter the fields that should be highlighted.
193 *
194 * @since 3.5.1
195 * @hook ep_highlighting_fields
196 * @param {array} $fields Highlighting fields
197 * @param {array} $formatted_args array
198 * @param {array} $args WP_Query args
199 * @return {array} New Highlighting fields
200 */
201 $fields_to_highlight = apply_filters(
202 'ep_highlighting_fields',
203 [ 'post_title', 'post_content' ],
204 $formatted_args,
205 $args
206 );
207
208 // define the tag to use
209 $current_tag = $settings['highlight_tag'];
210
211 /**
212 * Filter the tag that wraps the search highlighted term
213 *
214 * @since 3.5
215 * @hook ep_highlighting_tag
216 * @param {string} $current_tag Highlighting tag
217 * @return {string} New highlighting tag
218 */
219 $highlight_tag = apply_filters( 'ep_highlighting_tag', $current_tag );
220
221 /**
222 * Filter class applied to search highlight tags
223 *
224 * @since 3.5
225 * @hook ep_highlighting_class
226 * @param {string} $class Highlighting class
227 * @return {string} New highlighting class
228 */
229 $highlight_class = apply_filters( 'ep_highlighting_class', 'ep-highlight' );
230
231 // tags
232 $opening_tag = '<' . $highlight_tag . " class='" . $highlight_class . "'>";
233 $closing_tag = '</' . $highlight_tag . '>';
234
235 foreach ( $fields_to_highlight as $field ) {
236 $formatted_args['highlight']['fields'][ $field ] = [
237 'pre_tags' => [ $opening_tag ],
238 'post_tags' => [ $closing_tag ],
239 'type' => 'plain',
240 /**
241 * Filter the maximum number of fragments highlighted for a searched field.
242 *
243 * @since 4.7.2
244 * @hook ep_highlight_number_of_fragments
245 * @param {int} $max_fragments Maximum number of fragments for field.
246 * @param {string} $field Search field being setup.
247 * @return {int} New maximum number of fragments to highlight for the searched field.
248 */
249 'number_of_fragments' => apply_filters( 'ep_highlight_number_of_fragments', 0, $field ),
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 ( ! empty( $settings['highlight_excerpt'] ) && true === $settings['highlight_excerpt'] ) {
270 remove_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
271 add_filter( 'get_the_excerpt', [ $this, 'ep_highlight_excerpt' ], 10, 2 );
272 add_filter( 'ep_highlighting_fields', [ $this, 'ep_highlight_add_excerpt_field' ] );
273 }
274 }
275
276 /**
277 * Called by allow_excerpt_html
278 * logic for the excerpt filter allowing the currently selected tag.
279 *
280 * @param string $text excerpt string
281 * @param WP_Post $post Post Object
282 *
283 * @return string $text the new excerpt
284 */
285 public function ep_highlight_excerpt( $text, $post ) {
286
287 $settings = $this->get_settings();
288
289 // reproduces wp_trim_excerpt filter, preserving the excerpt_more and excerpt_length filters
290 if ( '' === $text ) {
291 $text = get_the_content( '', false, $post );
292 $text = apply_filters( 'the_content', $text );
293 $text = str_replace( '\]\]\>', ']]&gt;', $text );
294 $text = strip_tags( $text, '<' . esc_html( $settings['highlight_tag'] ) . '>' );
295
296 // use the defined length, if already applied...
297 $excerpt_length = apply_filters( 'excerpt_length', 55 );
298
299 // use defined excerpt_more filter if it is used
300 $excerpt_more = apply_filters( 'excerpt_more', $text );
301
302 $excerpt_more = $excerpt_more !== $text ? $excerpt_more : '[&hellip;]';
303
304 $words = explode( ' ', $text, $excerpt_length + 1 );
305 if ( count( $words ) > $excerpt_length ) {
306 array_pop( $words );
307 array_push( $words, $excerpt_more );
308 $text = implode( ' ', $words );
309 }
310 }
311
312 return $text;
313 }
314
315 /**
316 * Add `post_content` to the list of fields to highlight.
317 *
318 * @since 3.5.1
319 * @param array $fields_to_highlight The list of fields to highlight.
320 * @return array
321 */
322 public function ep_highlight_add_excerpt_field( $fields_to_highlight ) {
323 $fields_to_highlight[] = 'post_excerpt';
324 return $fields_to_highlight;
325 }
326
327 /**
328 * Helper filter to check if the tag is allowed.
329 *
330 * @param string $tag - html tag
331 * @return string
332 */
333 public function get_highlighting_tag( $tag ) {
334 if ( ! in_array( $tag, self::$default_highlight_tags, true ) ) {
335 $tag = 'mark';
336 }
337
338 return $tag;
339 }
340
341 /**
342 * Sanitizes our highlighting settings.
343 *
344 * @param array $settings Array of current settings
345 * @return mixed
346 */
347 public function sanitize_highlighting_settings( $settings ) {
348 if ( ! empty( $settings['search']['highlight_excerpt'] ) ) {
349 $settings['search']['highlight_excerpt'] = (bool) $settings['search']['highlight_excerpt'];
350 }
351
352 if ( ! empty( $settings['search']['highlight_enabled'] ) ) {
353 $settings['search']['highlight_enabled'] = (bool) $settings['search']['highlight_enabled'];
354 }
355
356 return $settings;
357 }
358
359 /**
360 * Returns searchable post types for the current site
361 *
362 * @since 1.9
363 * @return mixed|void
364 */
365 public function get_searchable_post_types() {
366 $post_types = get_post_types( array( 'exclude_from_search' => false ) );
367
368 /**
369 * Don't search attachments by default
370 *
371 * @since 3.0
372 */
373 unset( $post_types['attachment'] );
374
375 /**
376 * Filter searchable post types
377 *
378 * @hook ep_searchable_post_types
379 * @param {array} $post_types Post types
380 * @return {array} New post types
381 */
382 return apply_filters( 'ep_searchable_post_types', $post_types );
383 }
384
385 /**
386 * Make sure we don't search for "any" on a search query
387 *
388 * @param string $post_type Post type
389 * @param WP_Query $query WP Query
390 * @return string|array
391 */
392 public function filter_query_post_type_for_search( $post_type, $query ) {
393 if ( 'any' === $post_type && $query->is_search() ) {
394 $searchable_post_types = $this->get_searchable_post_types();
395
396 // If we have no searchable post types, there's no point going any further
397 if ( empty( $searchable_post_types ) ) {
398
399 // Have to return something or it improperly calculates the found_posts
400 return false;
401 }
402
403 // Conform the post types array to an acceptable format for ES
404 $post_types = [];
405
406 foreach ( $searchable_post_types as $type ) {
407 $post_types[] = $type;
408 }
409
410 // These are now the only post types we will search
411 $post_type = $post_types;
412 }
413
414 return $post_type;
415 }
416
417 /**
418 * Returns true/false if decaying is/isn't enabled
419 *
420 * @param array $args WP_Query args
421 *
422 * @return bool
423 */
424 public function is_decaying_enabled( $args = [] ) {
425 $settings = $this->get_settings();
426
427 $is_decaying_enabled = (bool) $settings['decaying_enabled'];
428
429 /**
430 * Filter to modify decaying
431 *
432 * @hook ep_is_decaying_enabled
433 * @since 4.6.0
434 * @param {bool} $is_decaying_enabled Whether decay by date is enabled or not
435 * @param {array} $settings Settings
436 * @param {array} $args WP_Query args
437 * @return {bool} Decaying
438 */
439 return apply_filters( 'ep_is_decaying_enabled', $is_decaying_enabled, $settings, $args );
440 }
441
442 /**
443 * Weight more recent content in searches
444 *
445 * @param array $formatted_args Formatted ES args
446 * @param array $args WP_Query args
447 * @since 2.1
448 * @return array
449 */
450 public function weight_recent( $formatted_args, $args ) {
451 if ( empty( $args['s'] ) ) {
452 return $formatted_args;
453 }
454
455 if ( ! $this->is_decaying_enabled( $args ) ) {
456 return $formatted_args;
457 }
458
459 /**
460 * Filter search date weighting scale
461 *
462 * @hook epwr_decay_function
463 * @param {string} $decay_function Current decay function
464 * @param {array} $formatted_args Formatted Elasticsearch arguments
465 * @param {array} $args WP_Query arguments
466 * @return {string} New decay function
467 */
468 $decay_function = apply_filters( 'epwr_decay_function', 'exp', $formatted_args, $args );
469
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 <div class="field">
621 <div class="field-name status"><?php esc_html_e( 'Weight results by date', 'elasticpress' ); ?></div>
622 <div class="input-wrap">
623 <label><input name="settings[decaying_enabled]" type="radio" <?php checked( (bool) $settings['decaying_enabled'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
624 <label><input name="settings[decaying_enabled]" type="radio" <?php checked( ! (bool) $settings['decaying_enabled'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label><br>
625 <?php
626 /**
627 * Fires after the default Weight results by date settings
628 *
629 * @since 4.6.0
630 * @hook ep_weight_settings_after_search
631 * @param {array} $settings settings array
632 */
633 do_action( 'ep_weight_settings_after_search', $settings );
634 ?>
635 </div>
636 </div>
637 <div class="field">
638 <div class="field-name status"><?php esc_html_e( 'Highlighting status', 'elasticpress' ); ?></div>
639 <div class="input-wrap">
640 <label><input name="settings[highlight_enabled]" type="radio" <?php checked( (bool) $settings['highlight_enabled'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
641 <label><input name="settings[highlight_enabled]" type="radio" <?php checked( ! (bool) $settings['highlight_enabled'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
642 <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>
643 </div>
644 </div>
645 <div class="field">
646 <label for="highlight-tag" class="field-name status"><?php echo esc_html_e( 'Highlight tag ', 'elasticpress' ); ?></label>
647 <div class="input-wrap">
648 <select id="highlight-tag" name="settings[highlight_tag]">
649 <?php
650 foreach ( self::$default_highlight_tags as $option ) :
651 echo '<option value="' . esc_attr( $option ) . '" ' . selected( $option, $settings['highlight_tag'] ) . '>' . esc_html( $option ) . '</option>';
652 endforeach;
653 ?>
654 </select>
655 </div>
656 </div>
657
658 <div class="field">
659 <div class="field-name status"><?php esc_html_e( 'Excerpt highlighting', 'elasticpress' ); ?></div>
660 <div class="input-wrap">
661 <label><input name="settings[highlight_excerpt]" type="radio" <?php checked( (bool) $settings['highlight_excerpt'] ); ?> value="1"><?php esc_html_e( 'Enabled', 'elasticpress' ); ?></label><br>
662 <label><input name="settings[highlight_excerpt]" type="radio" <?php checked( ! (bool) $settings['highlight_excerpt'] ); ?> value="0"><?php esc_html_e( 'Disabled', 'elasticpress' ); ?></label>
663 <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>
664 </div>
665 </div>
666
667 <?php if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) : ?>
668 <br class="clear">
669 <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>
670 <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>
671 <?php endif; ?>
672
673 <?php
674 }
675
676 /**
677 * Registers post meta for exclude from search feature.
678 */
679 public function register_meta() {
680 register_post_meta(
681 '',
682 'ep_exclude_from_search',
683 [
684 'show_in_rest' => true,
685 'single' => true,
686 'type' => 'boolean',
687 ]
688 );
689 }
690
691 /**
692 * Enqueue block editor assets.
693 */
694 public function enqueue_block_editor_assets() {
695 global $post;
696
697 if ( ! $post instanceof \WP_Post ) {
698 return;
699 }
700
701 wp_enqueue_script(
702 'ep-search-editor',
703 EP_URL . '/dist/js/search-editor-script.js',
704 Utils\get_asset_info( 'search-editor-script', 'dependencies' ),
705 Utils\get_asset_info( 'search-editor-script', 'version' ),
706 true
707 );
708 }
709
710 /**
711 * Exclude posts based on ep_exclude_from_search post meta.
712 *
713 * @param array $filters Filters to be applied to the query
714 * @param array $args WP Query args
715 * @param WP_Query $query WP Query object
716 */
717 public function exclude_posts_from_search( $filters, $args, $query ) {
718 $bypass_exclusion_from_search = is_admin() || ! $query->is_search();
719 /**
720 * Filter whether the exclusion from the "exclude from search" checkbox should be applied
721 *
722 * @since 4.4.0
723 * @hook ep_bypass_exclusion_from_search
724 * @param {bool} $bypass_exclusion_from_search True means all posts will be returned
725 * @param {WP_Query} $query WP Query
726 * @return {bool} New $bypass_exclusion_from_search value
727 */
728 if ( apply_filters( 'ep_bypass_exclusion_from_search', $bypass_exclusion_from_search, $query ) ) {
729 return $filters;
730 }
731
732 $filters[] = [
733 'bool' => [
734 'must_not' => [
735 [
736 'terms' => [
737 'meta.ep_exclude_from_search.raw' => [ '1' ],
738 ],
739 ],
740 ],
741 ],
742 ];
743
744 return $filters;
745 }
746
747 /**
748 * Outputs the checkbox to exclude a post from search.
749 *
750 * @param WP_POST $post Post object.
751 */
752 public function output_exclude_from_search_setting( $post ) {
753 $searchable_post_types = $this->get_searchable_post_types();
754 if ( ! in_array( $post->post_type, $searchable_post_types, true ) ) {
755 return;
756 }
757 ?>
758 <div class="misc-pub-section">
759 <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 ) ); ?>>
760 <label for="ep_exclude_from_search"><?php esc_html_e( 'Exclude from search results', 'elasticpress' ); ?></label>
761 <p class="howto">
762 <?php if ( 'attachment' === $post->post_type ) : ?>
763 <?php esc_html_e( 'Excludes this media from the results of your site\'s search form while ElasticPress is active.', 'elasticpress' ); ?>
764 <?php else : ?>
765 <?php esc_html_e( 'Excludes this post from the results of your site\'s search form while ElasticPress is active.', 'elasticpress' ); ?>
766 <?php endif; ?>
767 </p>
768 <?php wp_nonce_field( 'save-exclude-from-search', 'ep-exclude-from-search-nonce' ); ?>
769 </div>
770 <?php
771 }
772
773 /**
774 * Saves exclude from search meta.
775 *
776 * @param int $post_id The post ID.
777 */
778 public function save_exclude_from_search_meta( $post_id ) {
779 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
780 return;
781 }
782
783 if ( ! isset( $_POST['ep-exclude-from-search-nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['ep-exclude-from-search-nonce'] ), 'save-exclude-from-search' ) ) {
784 return;
785 }
786
787 if ( ! current_user_can( 'edit_post', $post_id ) ) {
788 return;
789 }
790
791 if ( isset( $_POST['ep_exclude_from_search'] ) ) {
792 update_post_meta( $post_id, 'ep_exclude_from_search', true );
793 } else {
794 delete_post_meta( $post_id, 'ep_exclude_from_search' );
795 }
796
797 }
798
799 /**
800 * If WP_Query has unsupported orderby, skip ES query integration and use the WP query instead.
801 *
802 * @param bool $skip Whether to skip ES query integration
803 * @param \WP_Query $query WP_Query object
804 *
805 * @since 4.5
806 * @return bool
807 */
808 public function skip_query_integration( $skip, $query ) {
809 if ( ! $query instanceof \WP_Query ) {
810 return $skip;
811 }
812
813 $unsupported_orderby = [
814 'post__in',
815 'post_name__in',
816 'post_parent__in',
817 'parent',
818 ];
819
820 $orderby = is_string( $query->get( 'orderby' ) ) ? explode( ' ', $query->get( 'orderby' ) ) : $query->get( 'orderby', 'date' );
821
822 $parse_orderby = array();
823 foreach ( $orderby as $key => $value ) {
824 $parse_orderby[] = is_string( $key ) ? $key : $value;
825 }
826
827 if ( array_intersect( $parse_orderby, $unsupported_orderby ) ) {
828 return true;
829 }
830
831 return $skip;
832 }
833 }
834