PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
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 / Weighting.php

Weighting.php in ElasticPress 5.3.5, at includes/classes/Feature/Search/Weighting.php

855 lines 25.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Weighting dashboard for ElasticPress
4 *
5 * @package elasticpress
6 */
7
8 namespace ElasticPress\Feature\Search;
9
10 use ElasticPress\Features;
11 use ElasticPress\Utils;
12
13 /**
14 * Controls search weighting and search fields dashboard
15 *
16 * @package ElasticPress\Feature\Search
17 */
18 class Weighting {
19
20 /**
21 * Sets up the weighting module
22 */
23 public function setup() {
24 /**
25 * Filter to disable loading of Search weighting engine.
26 *
27 * @hook ep_disable_search_weighting
28 * @since 4.0
29 * @param bool Whether to disable search weighting engine. Defaults to false.
30 * @return bool Whether to disable search weighting engine.
31 */
32 if ( apply_filters( 'ep_disable_search_weighting', false ) ) {
33 return;
34 }
35
36 add_action( 'admin_menu', [ $this, 'add_weighting_submenu_page' ], 15 );
37 add_filter( 'ep_formatted_args', [ $this, 'do_weighting' ], 20, 2 ); // After date decay, etc are injected
38 add_filter( 'ep_query_weighting_fields', [ $this, 'adjust_weight_for_cross_fields' ], 10, 5 );
39 add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
40 add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
41 }
42
43 /**
44 * Returns a grouping of all the fields that support weighting for the post type
45 *
46 * @param string $post_type Post type
47 *
48 * @return array
49 */
50 public function get_weightable_fields_for_post_type( $post_type ) {
51 $fields = array(
52 'attributes' => array(
53 'label' => __( 'Attributes', 'elasticpress' ),
54 'children' => array(
55 'post_title' => array(
56 'key' => 'post_title',
57 'label' => 'Title',
58 ),
59 'post_content' => array(
60 'key' => 'post_content',
61 'label' => 'Content',
62 ),
63 'post_excerpt' => array(
64 'key' => 'post_excerpt',
65 'label' => 'Excerpt',
66 ),
67 'author_name' => array(
68 'key' => 'author_name',
69 'label' => 'Author',
70 ),
71 ),
72 ),
73 );
74
75 $public_taxonomies = get_taxonomies(
76 [
77 'public' => true,
78 ]
79 );
80
81 $post_type_taxonomies = get_object_taxonomies( $post_type );
82
83 $taxonomies = array_intersect( $public_taxonomies, $post_type_taxonomies );
84
85 if ( ! empty( $taxonomies ) ) {
86 $fields['taxonomies'] = [
87 'label' => __( 'Taxonomies', 'elasticpress' ),
88 'children' => [],
89 ];
90
91 foreach ( $taxonomies as $taxonomy ) {
92 $key = "terms.{$taxonomy}.name";
93 $taxonomy_object = get_taxonomy( $taxonomy );
94
95 $fields['taxonomies']['children'][ $key ] = [
96 'key' => $key,
97 'label' => $taxonomy_object->labels->name,
98 ];
99 }
100 }
101
102 $fields['ep_metadata'] = [
103 'label' => 'Metadata',
104 'children' => [],
105 ];
106
107 $empty_post = new \WP_Post( new \stdClass() );
108
109 $empty_post->post_type = $post_type;
110
111 /** This filter is documented in includes/classes/Indexable/Post/Post.php */
112 $allowed_protected_keys = apply_filters( 'ep_prepare_meta_allowed_protected_keys', [], $empty_post );
113
114 sort( $allowed_protected_keys, SORT_STRING );
115
116 /** This filter is documented in includes/classes/Indexable/Post/Post.php */
117 $excluded_public_keys = apply_filters( 'ep_prepare_meta_excluded_public_keys', [], $empty_post );
118
119 foreach ( $allowed_protected_keys as $meta_key ) {
120 $key = "meta.$meta_key.value";
121
122 if ( in_array( $key, $excluded_public_keys, true ) ) {
123 continue;
124 }
125
126 $required = in_array( $meta_key, $allowed_protected_keys, true );
127
128 $fields['ep_metadata']['children'][ $key ] = [
129 'key' => $key,
130 'label' => $meta_key,
131 'required' => $required,
132 ];
133 }
134
135 /**
136 * Filter weighting fields for a post type
137 *
138 * @hook ep_weighting_fields_for_post_type
139 * @param {array} $fields Current weighting fields
140 * @param {string} $post_type Current post type
141 * @return {array} New fields
142 */
143 return apply_filters( 'ep_weighting_fields_for_post_type', $fields, $post_type );
144 }
145
146 /**
147 * Get weightable fields for all searchable post types.
148 *
149 * @since 5.0.0
150 * @return array
151 */
152 public function get_weightable_fields() {
153 $weightable = array();
154 $post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
155
156 foreach ( $post_types as $post_type ) {
157 $post_type_object = get_post_type_object( $post_type );
158 $post_type_labels = get_post_type_labels( $post_type_object );
159
160 $weightable_fields = $this->get_weightable_fields_for_post_type( $post_type );
161
162 $groups = [];
163 $fields = [];
164
165 foreach ( $weightable_fields as $group => $weightable_field ) {
166 $groups[] = [
167 'key' => $group,
168 'label' => $weightable_field['label'],
169 ];
170
171 foreach ( $weightable_field['children'] as $field ) {
172 $fields[] = [
173 'group' => $group,
174 'key' => $field['key'],
175 'label' => $field['label'],
176 'required' => isset( $field['required'] ) ? $field['required'] : false,
177 ];
178 }
179 }
180
181 $weightable[] = [
182 'key' => $post_type,
183 'label' => $post_type_labels->menu_name,
184 'groups' => $groups,
185 'fields' => $fields,
186 ];
187 }
188
189 return $weightable;
190 }
191
192 /**
193 * Returns default settings for any post type
194 *
195 * Defaults to title, content, excerpt, and author name enabled with zero weight
196 *
197 * @param string $post_type Post Type we need settings for
198 *
199 * @return array Defaults for post type
200 */
201 public function get_post_type_default_settings( $post_type ) {
202 $post_type_defaults = [
203 'post_title' => [
204 'enabled' => true,
205 'weight' => 1,
206 ],
207 'post_content' => [
208 'enabled' => true,
209 'weight' => 1,
210 ],
211 'post_excerpt' => [
212 'enabled' => true,
213 'weight' => 1,
214 ],
215 'author_name' => [
216 'enabled' => true,
217 'weight' => 1,
218 ],
219 ];
220
221 $post_type_taxonomies = get_object_taxonomies( $post_type );
222
223 /**
224 * Filter install status
225 *
226 * Previous behavior had post_tag and category enabled by default, so if this is supported on the post type
227 * we add them as enabled by default
228 *
229 * @hook ep_weighting_default_enabled_taxonomies
230 * @param {array} $enabled_taxonomies Taxonomies that should be enabled by default
231 * @param {string} $post_type Post type slug
232 * @return {array} New taxonomies
233 * @since 3.6.5
234 */
235 $enabled_by_default = apply_filters( 'ep_weighting_default_enabled_taxonomies', [ 'post_tag', 'category' ], $post_type );
236
237 foreach ( $enabled_by_default as $default_tax ) {
238 if ( in_array( $default_tax, $post_type_taxonomies, true ) ) {
239 $post_type_defaults[ 'terms.' . $default_tax . '.name' ] = [
240 'enabled' => true,
241 'weight' => 1,
242 ];
243 }
244 }
245
246 /**
247 * Filter weighting defaults for post type
248 *
249 * @hook ep_weighting_default_post_type_weights
250 * @param {array} $post_type_defaults Current weighting defaults
251 * @param {string} $post_type Current post type
252 * @return {array} New defaults
253 */
254 return apply_filters( 'ep_weighting_default_post_type_weights', $post_type_defaults, $post_type );
255 }
256
257 /**
258 * Returns the current weighting configuration
259 *
260 * @return array
261 */
262 public function get_weighting_configuration() {
263 /**
264 * Filter weighting configuration
265 *
266 * @hook ep_weighting_configuration
267 * @param {array} $config Current configuration
268 * @return {array} New configuration
269 */
270 return apply_filters( 'ep_weighting_configuration', get_option( 'elasticpress_weighting', [] ) );
271 }
272
273 /**
274 * Returns the current weighting configuration with defaults for any
275 * missing fields.
276 *
277 * @return array Current weighting configuration with defaults.
278 * @since 5.0.0
279 */
280 public function get_weighting_configuration_with_defaults() {
281 $search = Features::factory()->get_registered_feature( 'search' );
282
283 $post_types = $search->get_searchable_post_types();
284 $weighting = $this->get_weighting_configuration();
285
286 foreach ( $post_types as $post_type ) {
287 $current = isset( $weighting[ $post_type ] ) ? $weighting[ $post_type ] : [];
288 $defaults = $this->get_post_type_default_settings( $post_type );
289
290 $weighting[ $post_type ] = wp_parse_args( $current, $defaults );
291 }
292
293 return $weighting;
294 }
295
296 /**
297 * Returns the current meta mode.
298 *
299 * @return array
300 * @since 5.0.0
301 */
302 public function get_meta_mode() {
303 /**
304 * Filter meta management mode.
305 *
306 * Setting the meta mode to 'auto' will restore the pre-5.0.0 behavior
307 * of syncing all public meta fields, but without the ability to
308 * add fields to make them searchable through the UI. Weighting settings
309 * will need to be changed, and a sync performed, any time this value
310 * is changed.
311 *
312 * @hook ep_meta_mode
313 * @since 5.0.0
314 * @param string $ep_meta_mode Meta mode.
315 * @return string New meta mode.
316 */
317 return apply_filters( 'ep_meta_mode', 'manual' );
318 }
319
320 /**
321 * Adds the submenu page for controlling weighting
322 */
323 public function add_weighting_submenu_page() {
324 $menu_slug = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && ! Utils\is_top_level_admin_context() ) ?
325 'elasticpress' :
326 'elasticpress-weighting';
327
328 add_submenu_page(
329 'elasticpress',
330 esc_html__( 'ElasticPress Search Fields & Weighting', 'elasticpress' ),
331 esc_html__( 'Search Fields & Weighting', 'elasticpress' ),
332 Utils\get_capability(),
333 $menu_slug,
334 [ $this, 'render_settings_page' ]
335 );
336 }
337
338 /**
339 * Renders the settings page that controls weighting
340 */
341 public function render_settings_page() {
342 include EP_PATH . '/includes/partials/header.php'; ?>
343 <div class="wrap">
344 <div id="ep-weighting-screen"></div>
345 </div>
346 <?php
347 }
348
349 /**
350 * Recursively renders each settings section and its children.
351 *
352 * @param string $post_type Current post type we're rendering
353 * @param array $field Current field to render
354 * @param array $current_values Current stored weighting values
355 *
356 * @deprecated
357 */
358 public function render_settings_section( $post_type, $field, $current_values ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
359 _doing_it_wrong(
360 __METHOD__,
361 esc_html( 'Weighting sections display are now handled via React components.' ),
362 'ElasticPress 5.0.0'
363 );
364 }
365
366 /**
367 * Handles processing the new weighting values and saving them
368 * to the elasticpress.io service.
369 *
370 * @deprecated
371 */
372 public function handle_save() {
373 _doing_it_wrong(
374 __METHOD__,
375 esc_html( 'Weighting settings are now updated using the REST API.' ),
376 'ElasticPress 5.0.0'
377 );
378 }
379
380 /**
381 * We need this method to test handle_save properly.
382 *
383 * @param string $redirect_url Redirect URL.
384 * @deprecated
385 */
386 protected function redirect( $redirect_url ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed,Generic.CodeAnalysis.UnusedFunctionParameter.Found
387 _doing_it_wrong(
388 __METHOD__,
389 esc_html( 'Weighting settings are now updated using the REST API, and do not redirect server-side.' ),
390 'ElasticPress 5.0.0'
391 );
392 }
393
394 /**
395 * Save weighting configuration for each searchable post_type.
396 *
397 * @param array $settings weighting settings
398 * @return void
399 * @since 3.4.1
400 * @deprecated
401 */
402 public function save_weighting_configuration( $settings ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed,Generic.CodeAnalysis.UnusedFunctionParameter.Found
403 _doing_it_wrong(
404 __METHOD__,
405 esc_html( 'Weighting sections display are now handled via React components.' ),
406 'ElasticPress 5.0.0'
407 );
408 }
409
410 /**
411 * Register REST routes.
412 *
413 * @return void
414 * @since 5.0.0
415 */
416 public function register_rest_routes() {
417 register_rest_route(
418 'elasticpress/v1',
419 'weighting',
420 [
421 'callback' => [ $this, 'update_weighting' ],
422 'methods' => 'POST',
423 'permission_callback' => function () {
424 return current_user_can( Utils\get_capability() );
425 },
426 ]
427 );
428 }
429
430 /**
431 * Enqueue scripts and styles.
432 *
433 * @since 5.3.3
434 * @return void
435 */
436 public function admin_enqueue_scripts() {
437 if ( 'weighting' !== \ElasticPress\Screen::factory()->get_current_screen() ) {
438 return;
439 }
440
441 wp_enqueue_style(
442 'ep_weighting_styles',
443 EP_URL . 'dist/css/weighting-script.css',
444 [ 'wp-components', 'wp-edit-post' ],
445 Utils\get_asset_info( 'weighting-script', 'version' )
446 );
447
448 wp_enqueue_script(
449 'ep_weighting_script',
450 EP_URL . 'dist/js/weighting-script.js',
451 Utils\get_asset_info( 'weighting-script', 'dependencies' ),
452 Utils\get_asset_info( 'weighting-script', 'version' ),
453 true
454 );
455
456 $api_url = esc_url_raw( rest_url( 'elasticpress/v1/weighting' ) );
457 $meta_mode = $this->get_meta_mode();
458 $weightable_fields = $this->get_weightable_fields();
459 $weighting_configuration = $this->get_weighting_configuration_with_defaults();
460
461 /**
462 * Filter weighting dashboard options.
463 *
464 * @hook ep_weighting_options
465 * @param {array} $data Weighting dashboard options
466 * @return {array} New options array
467 * @since 5.1.0
468 */
469 $data = apply_filters(
470 'ep_weighting_options',
471 [
472 'apiUrl' => $api_url,
473 'metaMode' => $meta_mode,
474 'weightableFields' => $weightable_fields,
475 'weightingConfiguration' => $weighting_configuration,
476 ]
477 );
478
479 wp_localize_script(
480 'ep_weighting_script',
481 'epWeighting',
482 $data
483 );
484
485 wp_set_script_translations( 'ep_weighting_script', 'elasticpress' );
486 }
487
488 /**
489 * Handles processing the new weighting values and saving them.
490 *
491 * @param \WP_Rest_Request $request REST API request.
492 * @return array
493 * @since 5.0.0
494 */
495 public function update_weighting( $request = null ) {
496 $meta_mode = $this->get_meta_mode();
497 $weighting = $request->get_json_params();
498
499 $post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
500
501 // Add default fields for post types not sent.
502 foreach ( $post_types as $post_type ) {
503 if ( ! isset( $weighting[ $post_type ] ) ) {
504 $weighting[ $post_type ] = [];
505 }
506 }
507
508 /**
509 * If metadata is not being managed manually, remove any custom
510 * fields that have not been registered as weightable fields.
511 */
512 if ( 'manual' !== $meta_mode ) {
513 foreach ( $weighting as $post_type => $fields ) {
514 $weightable_fields = $this->get_weightable_fields_for_post_type( $post_type );
515
516 foreach ( $fields as $key => $value ) {
517 $is_weightable = false;
518
519 foreach ( $weightable_fields as $group ) {
520 if ( isset( $group['children'][ $key ] ) ) {
521 $is_weightable = true;
522 continue;
523 }
524 }
525
526 if ( ! $is_weightable ) {
527 unset( $weighting[ $post_type ][ $key ] );
528 }
529 }
530 }
531 }
532
533 // Cleanup any post type sent (or added via filters) that does not exist.
534 foreach ( $weighting as $post_type => $fields ) {
535 if ( ! post_type_exists( $post_type ) ) {
536 unset( $weighting[ $post_type ] );
537 }
538 }
539
540 update_option( 'elasticpress_weighting', $weighting );
541
542 /**
543 * Fires right after the weighting configuration is saved.
544 *
545 * @since 3.5.x
546 * @hook ep_saved_weighting_configuration
547 */
548 do_action( 'ep_saved_weighting_configuration' );
549
550 return [
551 'data' => $weighting,
552 'success' => true,
553 ];
554 }
555
556 /**
557 * Iterates through arrays in the formatted args to find "fields" and injects weighting values
558 *
559 * @param array $fieldset Current subset of formatted ES args
560 * @param array $weights Weight configuration
561 */
562 public function recursively_inject_weights_to_fields( &$fieldset, $weights ) {
563 if ( ! is_array( $fieldset ) ) {
564 return;
565 }
566
567 if ( is_array( $fieldset ) && isset( $fieldset['fields'] ) ) {
568 // Add any fields to the search that aren't already in there (weighting handled in next step)
569 foreach ( $weights as $field => $settings ) {
570 if ( ! in_array( $field, $fieldset['fields'], true ) ) {
571 $fieldset['fields'][] = $field;
572 }
573 }
574
575 foreach ( $fieldset['fields'] as $key => $field ) {
576 if ( isset( $weights[ $field ]['enabled'] ) && false !== $weights[ $field ]['enabled'] ) {
577 $weight = (int) $weights[ $field ]['weight'];
578
579 if ( 0 !== $weight ) {
580 if ( 'author_name' === $field ) {
581 $field = 'post_author.display_name';
582 }
583
584 /**
585 * Filter fields and their weitghting as used in the Elasticsearch query.
586 *
587 * @hook ep_query_weighting_fields
588 * @param {string} $weighted_field The field and its weight as used in the ES query.
589 * @param {string} $field Field name
590 * @param {string} $weight Weight value
591 * @param {array} $fieldset Current subset of formatted ES args
592 * @param {array} $weights Weight configuration
593 * @return {array} New weighted field string
594 *
595 * @since 3.5.5
596 */
597 $fieldset['fields'][ $key ] = apply_filters(
598 'ep_query_weighting_fields',
599 "{$field}^{$weight}",
600 $field,
601 $weight,
602 $fieldset,
603 $weights
604 );
605 }
606 } else {
607 // this handles removing post_author.login field added in Post::format_args() if author search field has being disabled
608 if ( 'author_name' === $field ) {
609 $author_key = array_search( 'post_author.login', $fieldset['fields'], true );
610 if ( false !== $author_key ) {
611 unset( $fieldset['fields'][ $author_key ] );
612 }
613 }
614 unset( $fieldset['fields'][ $key ] );
615 }
616 // else: Leave anything that isn't explicitly disabled alone. Could have been added by search_fields, and if it is not present in the UI, we shouldn't touch it here
617
618 // If fieldset has fuzziness enabled and fuzziness is disabled for this field, unset the field
619 if ( isset( $fieldset['fuzziness'] ) && $fieldset['fuzziness'] && isset( $weights[ $field ]['fuzziness'] ) && false === $weights[ $field ]['fuzziness'] ) {
620 unset( $fieldset['fields'][ $key ] );
621 }
622 }
623
624 // Reindex the array
625 $fieldset['fields'] = array_values( $fieldset['fields'] );
626 } else {
627 foreach ( $fieldset as &$field ) {
628 $this->recursively_inject_weights_to_fields( $field, $weights );
629 }
630 }
631
632 // Most likely to occur with the ordering results not being allowed in fuzzy, and weighting turning off fields for this otherwise
633 if ( isset( $fieldset['fields'] ) && empty( $fieldset['fields'] ) ) {
634 $fieldset = null;
635 }
636 }
637
638 /**
639 * Determine if a post type has any fields enabled for search
640 *
641 * @param string $post_type Post Type
642 * @param array $args WP_Query args
643 * @return boolean true/false depending on any fields enabled == true
644 */
645 public function post_type_has_fields( $post_type, $args = [] ) {
646 $weight_config = $this->get_weighting_configuration();
647
648 /**
649 * Filter weighting configuration for search
650 *
651 * @hook ep_weighting_configuration_for_search
652 * @param {array} $weight_config Current weight config
653 * @param {array} $args WP Query arguments
654 * @return {array} New configuration
655 */
656 $weight_config = apply_filters( 'ep_weighting_configuration_for_search', $weight_config, $args );
657
658 if ( ! isset( $weight_config[ $post_type ] ) ) {
659 $weights = $this->get_post_type_default_settings( $post_type );
660 } else {
661 $weights = $weight_config[ $post_type ];
662 }
663
664 /**
665 * Filter fields considered in weighting
666 *
667 * Define keys which are irrelevant for this consideration, like `terms.ep_custom_result.name`.
668 *
669 * @hook ep_weighting_ignore_fields_in_consideration
670 * @param {array} $fields Current fields
671 * @return {array} New fields
672 */
673 $ignore_keys = apply_filters( 'ep_weighting_ignore_fields_in_consideration', [ 'terms.ep_custom_result.name' => true ] );
674 $fields = array_diff_key( $weights, $ignore_keys );
675
676 $found_enabled = false;
677 foreach ( $fields as $field ) {
678 if ( isset( $field['enabled'] ) && filter_var( $field['enabled'], FILTER_VALIDATE_BOOLEAN ) ) {
679 $found_enabled = true;
680 break;
681 }
682 }
683
684 return $found_enabled;
685 }
686
687 /**
688 * Adjusts the query for configured weighting values
689 *
690 * @param array $formatted_args Formatted ES args
691 * @param array $args WP_Query args
692 *
693 * @return array Formatted ES args
694 */
695 public function do_weighting( $formatted_args, $args ) {
696
697 /**
698 * If search fields is set on the query, we should use those instead of the weighting, since the query was
699 * overridden by some custom code
700 */
701 if ( isset( $args['search_fields'] ) && ! empty( $args['search_fields'] ) ) {
702 return $formatted_args;
703 }
704
705 $weight_config = $this->get_weighting_configuration();
706
707 /**
708 * Filter weighting configuration for search
709 *
710 * @hook ep_weighting_configuration_for_search
711 * @param {array} $weight_config Current weight config
712 * @param {array} $args WP Query arguments
713 * @return {array} New configuration
714 */
715 $weight_config = apply_filters( 'ep_weighting_configuration_for_search', $weight_config, $args );
716
717 $should_do_weighting = Utils\is_integrated_request( 'weighting', [ 'public', 'rest' ] ) && ! empty( $args['s'] );
718
719 /**
720 * Filter whether to enable weighting configuration
721 *
722 * @hook ep_enable_do_weighting
723 * @since 4.2.2
724 * @param {bool} Whether to enable weight config, defaults to true for search requests that are public or REST
725 * @param {array} $weight_config Current weight config
726 * @param {array} $args WP Query arguments
727 * @param {array} $formatted_args Formatted ES arguments
728 * @return {bool} Whether to use weighting configuration
729 */
730 if ( apply_filters( 'ep_enable_do_weighting', $should_do_weighting, $weight_config, $args, $formatted_args ) ) {
731 $formatted_args = $this->apply_weighting( $formatted_args, $args, $weight_config );
732 }
733
734 return $formatted_args;
735 }
736
737 /**
738 * Applies weighting based on ES args
739 *
740 * @since 4.2.2
741 * @param array $formatted_args Formatted ES args
742 * @param array $args WP_Query args
743 * @param array $weight_config Weight configuration to apply
744 *
745 * @return array $formatted_args Formatted ES args with weightings applied
746 */
747 protected function apply_weighting( $formatted_args, $args, $weight_config ) {
748 /*
749 * This section splits up the single query clause for all post types into separate nested clauses (one for each post type)
750 * which then get combined into one result set. By having separate clauses for each post type, we can then
751 * weight fields such as post_title per post type so that we can have fine grained control over weights by post
752 * type, rather than globally on the query
753 */
754 $new_query = [
755 'bool' => [
756 'should' => [],
757 ],
758 ];
759
760 // grab the query and keep track of whether or not it is nested in a function score
761 $function_score = isset( $formatted_args['query']['function_score'] );
762 $query = $function_score ? $formatted_args['query']['function_score']['query'] : $formatted_args['query'];
763
764 foreach ( (array) $args['post_type'] as $post_type ) {
765 if ( false === $this->post_type_has_fields( $post_type, $args ) ) {
766 continue;
767 }
768 // Copy the query, so we can set specific weight values
769 $current_query = $query;
770
771 if ( isset( $weight_config[ $post_type ] ) ) {
772 // Find all "fields" values and inject weights for the current post type
773 $this->recursively_inject_weights_to_fields( $current_query, $weight_config[ $post_type ] );
774 } else {
775 // Use the default values for the post type
776 $this->recursively_inject_weights_to_fields( $current_query, $this->get_post_type_default_settings( $post_type ) );
777 }
778
779 // Check for any segments with null fields from recursively_inject function and remove them
780 if ( isset( $current_query['bool'] ) && isset( $current_query['bool']['should'] ) ) {
781 foreach ( $current_query['bool']['should'] as $index => $current_bool_should ) {
782 if ( isset( $current_bool_should['multi_match'] ) && null === $current_bool_should['multi_match'] ) {
783 unset( $current_query['bool']['should'][ $index ] );
784 }
785 }
786 }
787
788 /**
789 * Filter weighting query for a post type
790 *
791 * @hook ep_weighted_query_for_post_type
792 * @param {array} $query Weighting query
793 * @param {string} $post_type Post type
794 * @param {array} $args WP Query arguments
795 * @return {array} New query
796 */
797 $new_query['bool']['should'][] = apply_filters(
798 'ep_weighted_query_for_post_type',
799 [
800 'bool' => [
801 'must' => [
802 $current_query,
803 ],
804 'filter' => [
805 [
806 'match' => [
807 'post_type.raw' => $post_type,
808 ],
809 ],
810 ],
811 ],
812 ],
813 $post_type,
814 $args
815 );
816 }
817
818 // put the new query back in the correct location
819 if ( $function_score ) {
820 $formatted_args['query']['function_score']['query'] = $new_query;
821 } else {
822 $formatted_args['query'] = $new_query;
823 }
824
825 /**
826 * Hook after weighting is added to Elasticsearch query
827 *
828 * @hook ep_weighting_added
829 * @param {array} $formatted_args Elasticsearch query
830 * @param {array} $args WP Query arguments
831 */
832 do_action( 'ep_weighting_added', $formatted_args, $args );
833
834 return $formatted_args;
835 }
836
837 /**
838 * Adjust weighting when the type is cross_fields, as it just works with weight = 1.
839 *
840 * @since 4.0.0
841 *
842 * @param string $weighted_field The field and its weight as used in the ES query.
843 * @param string $field Field name
844 * @param string $weight Weight value
845 * @param array $fieldset Current subset of formatted ES args
846 * @return array New weighted field string
847 */
848 public function adjust_weight_for_cross_fields( $weighted_field, $field, $weight, $fieldset ) {
849 if ( ! empty( $fieldset['type'] ) && 'cross_fields' === $fieldset['type'] ) {
850 $weighted_field = "{$field}^1";
851 }
852 return $weighted_field;
853 }
854 }
855