← All changes
|
includes/classes/Indexable/Post/QueryIntegration.php
+116
-32
4.2.0
→
5.3.5
View file →
| @@ -7,11 +7,11 @@ | ||
| 7 | 7 | */ |
| 8 | 8 | |
| 9 | 9 | namespace ElasticPress\Indexable\Post; |
| 10 | 10 | |
| 11 | -use ElasticPress\Indexables as Indexables; | |
| 12 | -use \WP_Query as WP_Query; | |
| 13 | -use ElasticPress\Utils as Utils; | |
| 11 | +use WP_Query; | |
| 12 | +use ElasticPress\Indexables; | |
| 13 | +use ElasticPress\Utils; | |
| 14 | 14 | |
| 15 | 15 | if ( ! defined( 'ABSPATH' ) ) { |
| 16 | 16 | // @codeCoverageIgnoreStart |
| 17 | 17 | exit; // Exit if accessed directly. |
| @@ -38,11 +38,23 @@ | ||
| 38 | 38 | * @since 0.9 |
| 39 | 39 | * @since 3.6.0 Added $indexable_slug |
| 40 | 40 | */ |
| 41 | 41 | public function __construct( $indexable_slug = 'post' ) { |
| 42 | + /** | |
| 43 | + * Filter whether to enable query integration during indexing | |
| 44 | + * | |
| 45 | + * @since 4.5.2 | |
| 46 | + * @hook ep_enable_query_integration_during_indexing | |
| 47 | + * | |
| 48 | + * @param {bool} $enable To allow query integration during indexing | |
| 49 | + * @param {string} $indexable_slug Indexable slug | |
| 50 | + * @return {bool} New value | |
| 51 | + */ | |
| 52 | + $allow_query_integration_during_indexing = apply_filters( 'ep_enable_query_integration_during_indexing', false, $indexable_slug ); | |
| 53 | + | |
| 42 | 54 | // Ensure that we are currently allowing ElasticPress to override the normal WP_Query |
| 43 | 55 | // Indexable->is_full_reindexing() is not available at this point yet, so using the IndexHelper version of it. |
| 44 | - if ( \ElasticPress\IndexHelper::factory()->is_full_reindexing( $indexable_slug, get_current_blog_id() ) ) { | |
| 56 | + if ( \ElasticPress\IndexHelper::factory()->is_full_reindexing( $indexable_slug, get_current_blog_id() ) && ! $allow_query_integration_during_indexing ) { | |
| 45 | 57 | return; |
| 46 | 58 | } |
| 47 | 59 | |
| 48 | 60 | // Add header |
| @@ -51,9 +63,9 @@ | ||
| 51 | 63 | // Query ES for posts |
| 52 | 64 | add_filter( 'posts_pre_query', array( $this, 'get_es_posts' ), 10, 2 ); |
| 53 | 65 | |
| 54 | 66 | // Properly restore blog if necessary |
| 55 | - add_action( 'loop_end', array( $this, 'maybe_restore_blog' ), 10, 1 ); | |
| 67 | + add_action( 'loop_end', array( $this, 'maybe_restore_blog' ), 10 ); | |
| 56 | 68 | |
| 57 | 69 | // Properly switch to blog if necessary |
| 58 | 70 | add_action( 'the_post', array( $this, 'maybe_switch_to_blog' ), 10, 2 ); |
| 59 | 71 | |
| @@ -85,9 +97,9 @@ | ||
| 85 | 97 | return $query->num_posts; |
| 86 | 98 | } |
| 87 | 99 | |
| 88 | 100 | /** |
| 89 | - * Disables cache_results, adds header. | |
| 101 | + * Adds header. | |
| 90 | 102 | * |
| 91 | 103 | * @param WP_Query $query WP_Query instance |
| 92 | 104 | * @since 0.9 |
| 93 | 105 | */ |
| @@ -103,18 +115,8 @@ | ||
| 103 | 115 | if ( ! Indexables::factory()->get( 'post' )->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_query_integration', false, $query ) ) { |
| 104 | 116 | return; |
| 105 | 117 | } |
| 106 | 118 | |
| 107 | - /** | |
| 108 | - * `cache_results` defaults to false but can be enabled. | |
| 109 | - * | |
| 110 | - * @since 1.5 | |
| 111 | - */ | |
| 112 | - $query->set( 'cache_results', false ); | |
| 113 | - if ( ! empty( $query->query['cache_results'] ) ) { | |
| 114 | - $query->set( 'cache_results', true ); | |
| 115 | - } | |
| 116 | - | |
| 117 | 119 | if ( ! headers_sent() ) { |
| 118 | 120 | /** |
| 119 | 121 | * Manually setting a header as $wp_query isn't yet initialized when we |
| 120 | 122 | * call: add_filter('wp_headers', 'filter_wp_headers'); |
| @@ -176,18 +178,18 @@ | ||
| 176 | 178 | |
| 177 | 179 | $this->switched = false; |
| 178 | 180 | } |
| 179 | 181 | } |
| 180 | - | |
| 181 | 182 | } |
| 182 | 183 | |
| 183 | 184 | /** |
| 184 | 185 | * Make sure the correct blog is restored |
| 185 | 186 | * |
| 186 | - * @param WP_Query $query WP_Query instance | |
| 187 | + * @param WP_Query $query WP_Query instance | |
| 188 | + * | |
| 187 | 189 | * @since 0.9 |
| 188 | 190 | */ |
| 189 | - public function maybe_restore_blog( $query ) { | |
| 191 | + public function maybe_restore_blog( $query ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found | |
| 190 | 192 | if ( ! is_multisite() ) { |
| 191 | 193 | // @codeCoverageIgnoreStart |
| 192 | 194 | return; |
| 193 | 195 | // @codeCoverageIgnoreEnd |
| @@ -208,10 +210,8 @@ | ||
| 208 | 210 | * @since 3.0 |
| 209 | 211 | * @return string |
| 210 | 212 | */ |
| 211 | 213 | public function get_es_posts( $posts, $query ) { |
| 212 | - global $wpdb; | |
| 213 | - | |
| 214 | 214 | /** |
| 215 | 215 | * Filter to skip WP Query integration |
| 216 | 216 | * |
| 217 | 217 | * @hook ep_skip_query_integration |
| @@ -233,9 +233,9 @@ | ||
| 233 | 233 | * @param {string|array} $post_types Post types |
| 234 | 234 | * @param {WP_Query} $query WP Query object |
| 235 | 235 | * @return {string|array} New post types |
| 236 | 236 | */ |
| 237 | - $query_vars['post_type'] = apply_filters( 'ep_query_post_type', $query_vars['post_type'], $query ); | |
| 237 | + $query_vars['post_type'] = apply_filters( 'ep_query_post_type', $query_vars['post_type'] ?? '', $query ); | |
| 238 | 238 | |
| 239 | 239 | if ( 'any' === $query_vars['post_type'] ) { |
| 240 | 240 | unset( $query_vars['post_type'] ); |
| 241 | 241 | } |
| @@ -240,12 +240,16 @@ | ||
| 240 | 240 | unset( $query_vars['post_type'] ); |
| 241 | 241 | } |
| 242 | 242 | |
| 243 | 243 | /** |
| 244 | - * If not search and not set default to post. If not set and is search, use searchable post types | |
| 244 | + * If not search and not set, default to post. If not set and is search, use searchable post types. | |
| 245 | 245 | */ |
| 246 | 246 | if ( empty( $query_vars['post_type'] ) ) { |
| 247 | - if ( empty( $query_vars['s'] ) ) { | |
| 247 | + $tax_post_type = Utils\get_post_types_for_tax_query( $query ); | |
| 248 | + | |
| 249 | + if ( ! empty( $tax_post_type ) ) { | |
| 250 | + $query_vars['post_type'] = $tax_post_type; | |
| 251 | + } elseif ( empty( $query_vars['s'] ) ) { | |
| 248 | 252 | $query_vars['post_type'] = 'post'; |
| 249 | 253 | } else { |
| 250 | 254 | $query_vars['post_type'] = array_values( get_post_types( array( 'exclude_from_search' => false ) ) ); |
| 251 | 255 | } |
| @@ -272,12 +276,30 @@ | ||
| 272 | 276 | |
| 273 | 277 | if ( count( $new_posts ) < 1 ) { |
| 274 | 278 | |
| 275 | 279 | $scope = 'current'; |
| 280 | + | |
| 281 | + $site__in = ''; | |
| 282 | + $site__not_in = ''; | |
| 283 | + | |
| 276 | 284 | if ( ! empty( $query_vars['sites'] ) ) { |
| 277 | - $scope = $query_vars['sites']; | |
| 285 | + _deprecated_argument( __FUNCTION__, '4.4.0', esc_html__( 'sites is deprecated. Use site__in instead.', 'elasticpress' ) ); | |
| 278 | 286 | } |
| 279 | 287 | |
| 288 | + if ( ! empty( $query_vars['site__in'] ) || ! empty( $query_vars['sites'] ) ) { | |
| 289 | + $site__in = ! empty( $query_vars['site__in'] ) ? (array) $query_vars['site__in'] : (array) $query_vars['sites']; | |
| 290 | + | |
| 291 | + if ( in_array( 'all', $site__in, true ) ) { | |
| 292 | + $scope = 'all'; | |
| 293 | + } elseif ( in_array( 'current', $site__in, true ) ) { | |
| 294 | + $site__in = (array) get_current_blog_id(); | |
| 295 | + } | |
| 296 | + } | |
| 297 | + | |
| 298 | + if ( ! empty( $query_vars['site__not_in'] ) ) { | |
| 299 | + $site__not_in = (array) $query_vars['site__not_in']; | |
| 300 | + } | |
| 301 | + | |
| 280 | 302 | $formatted_args = Indexables::factory()->get( 'post' )->format_args( $query_vars, $query ); |
| 281 | 303 | |
| 282 | 304 | /** |
| 283 | 305 | * Filter post query scope |
| @@ -294,22 +316,44 @@ | ||
| 294 | 316 | $scope = 'current'; |
| 295 | 317 | // @codeCoverageIgnoreEnd |
| 296 | 318 | } |
| 297 | 319 | |
| 320 | + /** | |
| 321 | + * Disable the post cache when using a persistent object cache or querying | |
| 322 | + * across sites, where identical post IDs can cause cache collisions. | |
| 323 | + */ | |
| 324 | + if ( wp_using_ext_object_cache() || 'all' === $scope || ! empty( $site__in ) || ! empty( $site__not_in ) ) { | |
| 325 | + $query->set( 'cache_results', false ); | |
| 326 | + } | |
| 327 | + | |
| 298 | 328 | $index = null; |
| 299 | 329 | |
| 300 | 330 | if ( 'all' === $scope ) { |
| 301 | 331 | $index = Indexables::factory()->get( 'post' )->get_network_alias(); |
| 302 | - } elseif ( is_numeric( $scope ) ) { | |
| 303 | - $index = Indexables::factory()->get( 'post' )->get_index_name( (int) $scope ); | |
| 304 | - } elseif ( is_array( $scope ) ) { | |
| 332 | + } elseif ( ! empty( $site__in ) ) { | |
| 305 | 333 | $index = []; |
| 306 | 334 | |
| 307 | - foreach ( $scope as $site_id ) { | |
| 335 | + foreach ( $site__in as $site_id ) { | |
| 308 | 336 | $index[] = Indexables::factory()->get( 'post' )->get_index_name( $site_id ); |
| 309 | 337 | } |
| 310 | 338 | |
| 311 | 339 | $index = implode( ',', $index ); |
| 340 | + } elseif ( ! empty( $site__not_in ) ) { | |
| 341 | + | |
| 342 | + $sites = \get_sites( | |
| 343 | + array( | |
| 344 | + 'fields' => 'ids', | |
| 345 | + 'site__not_in' => $site__not_in, | |
| 346 | + ) | |
| 347 | + ); | |
| 348 | + foreach ( $sites as $site_id ) { | |
| 349 | + if ( ! Utils\is_site_indexable( $site_id ) ) { | |
| 350 | + continue; | |
| 351 | + } | |
| 352 | + $index[] = Indexables::factory()->get( 'post' )->get_index_name( $site_id ); | |
| 353 | + } | |
| 354 | + | |
| 355 | + $index = implode( ',', $index ); | |
| 312 | 356 | } |
| 313 | 357 | |
| 314 | 358 | $ep_query = Indexables::factory()->get( 'post' )->query_es( $formatted_args, $query->query_vars, $index, $query ); |
| 315 | 359 | |
| @@ -323,9 +367,10 @@ | ||
| 323 | 367 | |
| 324 | 368 | $found_documents = is_array( $ep_query['found_documents'] ) ? $ep_query['found_documents']['value'] : $ep_query['found_documents']; // 7.0+ have this as an array rather than int |
| 325 | 369 | $query->found_posts = $found_documents; |
| 326 | 370 | $query->num_posts = $query->found_posts; |
| 327 | - $query->max_num_pages = ceil( $found_documents / $query->get( 'posts_per_page' ) ); | |
| 371 | + $query->max_num_pages = -1 === $query->get( 'posts_per_page' ) ? 0 : ceil( $found_documents / $query->get( 'posts_per_page' ) ); | |
| 372 | + $query->suggested_terms = $this->maybe_sanitize_suggestion( $ep_query ); | |
| 328 | 373 | $query->elasticsearch_success = true; |
| 329 | 374 | |
| 330 | 375 | // Determine how we should format the results from ES based on the fields parameter. |
| 331 | 376 | $fields = $query->get( 'fields', '' ); |
| @@ -435,11 +480,17 @@ | ||
| 435 | 480 | ); |
| 436 | 481 | |
| 437 | 482 | foreach ( $post_return_args as $key ) { |
| 438 | 483 | if ( 'post_author' === $key ) { |
| 439 | - $post->$key = $post_array[ $key ]['id']; | |
| 484 | + if ( isset( $post_array[ $key ]['id'] ) ) { | |
| 485 | + $post->$key = $post_array[ $key ]['id']; | |
| 486 | + } | |
| 440 | 487 | } elseif ( isset( $post_array[ $key ] ) ) { |
| 441 | - $post->$key = $post_array[ $key ]; | |
| 488 | + if ( in_array( $key, [ 'terms', 'meta', 'post_meta' ], true ) && is_array( $post_array[ $key ] ) ) { | |
| 489 | + $post->$key = wp_json_encode( $post_array[ $key ] ); | |
| 490 | + } else { | |
| 491 | + $post->$key = $post_array[ $key ]; | |
| 492 | + } | |
| 442 | 493 | } |
| 443 | 494 | } |
| 444 | 495 | |
| 445 | 496 | /** |
| @@ -513,6 +564,39 @@ | ||
| 513 | 564 | $post->elasticsearch = true; // Super useful for debugging |
| 514 | 565 | $new_posts[] = $post; |
| 515 | 566 | } |
| 516 | 567 | return $new_posts; |
| 568 | + } | |
| 569 | + | |
| 570 | + /** | |
| 571 | + * Remove any suggestion that has a score lower than the minimum score. | |
| 572 | + * | |
| 573 | + * @since 4.6.0 | |
| 574 | + * @param array $ep_query The query array. | |
| 575 | + * @return array | |
| 576 | + */ | |
| 577 | + protected function maybe_sanitize_suggestion( $ep_query ) { | |
| 578 | + if ( ! isset( $ep_query['suggest']['ep_suggestion'], $ep_query['suggest']['ep_suggestion'][0] ) ) { | |
| 579 | + return []; | |
| 580 | + } | |
| 581 | + | |
| 582 | + $suggestion = $ep_query['suggest']['ep_suggestion'][0]; | |
| 583 | + | |
| 584 | + /** | |
| 585 | + * Filter the score for a suggestion. If the score is lower than this, it will be removed. | |
| 586 | + * | |
| 587 | + * @since 4.6.0 | |
| 588 | + * @param float $min_score The minimum score allowed. | |
| 589 | + * @return float | |
| 590 | + */ | |
| 591 | + $min_score = (float) apply_filters( 'ep_suggestion_minimum_score', 0.0001 ); | |
| 592 | + | |
| 593 | + $suggestion['options'] = array_filter( | |
| 594 | + $suggestion['options'], | |
| 595 | + function ( $option ) use ( $min_score ) { | |
| 596 | + return number_format( $option['score'], 10 ) > $min_score; | |
| 597 | + } | |
| 598 | + ); | |
| 599 | + | |
| 600 | + return $suggestion; | |
| 517 | 601 | } |
| 518 | 602 | } |