PluginProbe
ElasticPress / 5.0.0
ElasticPress v5.0.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 5.0.0, at includes/classes/Feature/Search/Search.php

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