From 37552b6f9404133577436805fe51940b6f4334bb Mon Sep 17 00:00:00 2001 From: Burhan Nasir Date: Wed, 5 Feb 2025 17:30:09 +0500 Subject: [PATCH] Add include, lower_limit_object_id and higher_limit_object_id for comments --- .../classes/Indexable/Comment/Comment.php | 124 ++++++++++++++---- includes/classes/Indexable/Post/Post.php | 20 +-- tests/php/indexables/TestComment.php | 37 ++++-- 3 files changed, 139 insertions(+), 42 deletions(-) diff --git a/includes/classes/Indexable/Comment/Comment.php b/includes/classes/Indexable/Comment/Comment.php index 08082cd4f..826d9e793 100644 --- a/includes/classes/Indexable/Comment/Comment.php +++ b/includes/classes/Indexable/Comment/Comment.php @@ -777,22 +777,30 @@ public function get_indexable_comment_status() { * @return array */ public function query_db( $args ) { - $defaults = [ - 'type' => $this->get_indexable_comment_types(), - 'status' => $this->get_indexable_comment_status(), - 'post_type' => Indexables::factory()->get( 'post' )->get_indexable_post_types(), - 'post_status' => Indexables::factory()->get( 'post' )->get_indexable_post_status(), - 'number' => $this->get_bulk_items_per_page(), - 'offset' => 0, - 'orderby' => 'comment_ID', - 'order' => 'desc', + 'type' => $this->get_indexable_comment_types(), + 'status' => $this->get_indexable_comment_status(), + 'post_type' => Indexables::factory()->get( 'post' )->get_indexable_post_types(), + 'post_status' => Indexables::factory()->get( 'post' )->get_indexable_post_status(), + 'number' => $this->get_bulk_items_per_page(), + 'offset' => 0, + 'orderby' => 'comment_ID', + 'order' => 'desc', + 'ep_indexing_advanced_pagination' => true, ]; if ( isset( $args['per_page'] ) ) { $args['number'] = $args['per_page']; } + if ( isset( $args['include'] ) ) { + $args['comment__in'] = $args['include']; + } + + if ( isset( $args['exclude'] ) ) { + $args['comment__not_in'] = $args['exclude']; + } + /** * Filter database arguments for comment query * @@ -809,23 +817,34 @@ public function query_db( $args ) { unset( $all_query_args['offset'] ); $all_query_args['count'] = true; - /** - * Filter database arguments for comment count query - * - * @hook ep_comment_all_query_db_args - * @param {array} $args Query arguments based to WP_Comment_Query - * @since 3.6.0 - * @return {array} New arguments - */ - $total_objects = get_comments( apply_filters( 'ep_comment_all_query_db_args', $all_query_args, $args ) ); - - if ( ! empty( $args['offset'] ) ) { - if ( (int) $args['offset'] >= $total_objects ) { - $total_objects = 0; - } + if ( isset( $args['comment__in'] ) || 0 < $args['offset'] ) { + // Disable advanced pagination. Not useful if only indexing specific IDs. + $args['ep_indexing_advanced_pagination'] = false; } - $query = new WP_Comment_Query( $args ); + // Enforce the following query args during advanced pagination to ensure things work correctly. + if ( $args['ep_indexing_advanced_pagination'] ) { + $args = array_merge( + $args, + [ + 'suppress_filters' => false, + 'orderby' => 'ID', + 'order' => 'DESC', + 'paged' => 1, + 'offset' => 0, + 'no_found_rows' => false, + ] + ); + add_filter( 'comments_clauses', array( $this, 'bulk_indexing_filter_comments_where' ), 9999, 2 ); + + $query = new WP_Comment_Query( $args ); + $total_objects = $query->found_comments; + + remove_filter( 'comments_clauses', array( $this, 'bulk_indexing_filter_comments_where' ), 9999, 2 ); + } else { + $query = new WP_Comment_Query( $args ); + $total_objects = $query->found_comments; + } if ( is_array( $query->comments ) ) { array_walk( $query->comments, [ $this, 'remap_comments' ] ); @@ -837,6 +856,50 @@ public function query_db( $args ) { ]; } + /** + * Manipulate the WHERE clause of the bulk indexing query to paginate by ID in order to avoid performance issues with SQL offset. + * + * @param array $clauses Array of clauses for the query. + * @param WP_Query $query \WP_Comment_Query object. + * @return array Updated array of clauses. + */ + public function bulk_indexing_filter_comments_where( $clauses, $query ) { + global $wpdb; + + $using_advanced_pagination = $this->get_query_var( $query, 'ep_indexing_advanced_pagination', false ); + + if ( $using_advanced_pagination ) { + $requested_upper_limit_id = $this->get_query_var( $query, 'ep_indexing_upper_limit_object_id', PHP_INT_MAX ); + $requested_lower_limit_post_id = $this->get_query_var( $query, 'ep_indexing_lower_limit_object_id', 0 ); + $last_processed_id = $this->get_query_var( $query, 'ep_indexing_last_processed_object_id', null ); + + // On the first loopthrough we begin with the requested upper limit ID. Afterwards, use the last processed ID to paginate. + $upper_limit_range_post_id = $requested_upper_limit_id; + if ( is_numeric( $last_processed_id ) ) { + $upper_limit_range_post_id = $last_processed_id - 1; + } + + // Sanitize. Abort if unexpected data at this point. + if ( ! is_numeric( $upper_limit_range_post_id ) || ! is_numeric( $requested_lower_limit_post_id ) ) { + return $clauses; + } + + $range = [ + 'upper_limit' => "{$wpdb->comments}.comment_ID <= {$upper_limit_range_post_id}", + 'lower_limit' => "{$wpdb->comments}.comment_ID >= {$requested_lower_limit_post_id}", + ]; + + // Skip the end range if it's unnecessary. + $skip_ending_range = 0 === $requested_lower_limit_post_id; + $where = $clauses['where']; + $where = $skip_ending_range ? " {$range['upper_limit']} AND {$where}" : " {$range['upper_limit']} AND {$range['lower_limit']} AND {$where}"; + + $clauses['where'] = $where; + } + + return $clauses; + } + /** * Prepare a comment document for indexing * @@ -1073,4 +1136,17 @@ protected function parse_orderby( $orderby, $order, $args ) { return $sort; } + + /** + * Retrieve a specific query variable from the query object. + * + * @param \WP_Comment_Query $query The query object. + * @param string $query_var The name of the query variable to retrieve. + * @param string $default_value The default value to return if the query variable is not set. Default is an empty string. + * + * @return mixed The value of the query variable if set, otherwise the default value. + */ + public function get_query_var( $query, $query_var, $default_value = '' ) { + return $query->query_vars[ $query_var ] ?? $default_value; + } } diff --git a/includes/classes/Indexable/Post/Post.php b/includes/classes/Indexable/Post/Post.php index c02201fd1..67eaf702e 100644 --- a/includes/classes/Indexable/Post/Post.php +++ b/includes/classes/Indexable/Post/Post.php @@ -134,14 +134,16 @@ public function query_db( $args ) { ]; } - /** - * Manipulate the WHERE clause of the bulk indexing query to paginate by ID in order to avoid performance issues with SQL offset. - * - * @param string $where The current $where clause. - * @param WP_Query $query WP_Query object. - * @return string WHERE clause with our pagination added if needed. - */ + /** + * Manipulate the WHERE clause of the bulk indexing query to paginate by ID in order to avoid performance issues with SQL offset. + * + * @param string $where The current $where clause. + * @param WP_Query $query WP_Query object. + * @return string WHERE clause with our pagination added if needed. + */ public function bulk_indexing_filter_posts_where( $where, $query ) { + global $wpdb; + $using_advanced_pagination = $query->get( 'ep_indexing_advanced_pagination', false ); if ( $using_advanced_pagination ) { @@ -161,8 +163,8 @@ public function bulk_indexing_filter_posts_where( $where, $query ) { } $range = [ - 'upper_limit' => "{$GLOBALS['wpdb']->posts}.ID <= {$upper_limit_range_post_id}", - 'lower_limit' => "{$GLOBALS['wpdb']->posts}.ID >= {$requested_lower_limit_post_id}", + 'upper_limit' => "{$wpdb->posts}.ID <= {$upper_limit_range_post_id}", + 'lower_limit' => "{$wpdb->posts}.ID >= {$requested_lower_limit_post_id}", ]; // Skip the end range if it's unnecessary. diff --git a/tests/php/indexables/TestComment.php b/tests/php/indexables/TestComment.php index 93efdf23b..7cef23cab 100644 --- a/tests/php/indexables/TestComment.php +++ b/tests/php/indexables/TestComment.php @@ -2179,25 +2179,33 @@ public function testWooCommerceReviewIndexing() { * @group comment */ public function testCommentIndexableQueryDb() { - $post_id = wp_insert_post( + ElasticPress\Features::factory()->deactivate_feature( 'woocommerce' ); + + $post_id = $this->ep_factory->post->create(); + + $comment_1_id = $this->ep_factory->comment->create( [ - 'post_name' => 'start-here', - 'post_status' => 'publish', + 'comment_post_ID' => $post_id, ] ); - wp_insert_comment( + $this->ep_factory->comment->create_many( + 2, [ - 'comment_content' => 'Test comment 1', 'comment_post_ID' => $post_id, ] ); - $product_id = wp_insert_post( + $product_id = $this->ep_factory->product->create( [ 'post_content' => 'product 1', - 'post_type' => 'product', - 'post_status' => 'publish', + ] + ); + + $this->ep_factory->comment->create( + [ + 'comment_post_ID' => $product_id, + 'comment_type' => 'review', ] ); @@ -2211,12 +2219,23 @@ public function testCommentIndexableQueryDb() { $comment_indexable = new \ElasticPress\Indexable\Comment\Comment(); + // Test only comments are returned. $results = $comment_indexable->query_db( [] ); - $this->assertArrayHasKey( 'objects', $results ); $this->assertArrayHasKey( 'total_objects', $results ); + $this->assertEquals( 3, $results['total_objects'] ); + // Test only 1 comment is returned. + $results = $comment_indexable->query_db( [ 'include' => $comment_1_id ] ); + $this->assertArrayHasKey( 'objects', $results ); + $this->assertArrayHasKey( 'total_objects', $results ); $this->assertEquals( 1, $results['total_objects'] ); + + // Test all comments are returned except the one with ID. + $results = $comment_indexable->query_db( [ 'exclude' => $comment_1_id ] ); + $this->assertArrayHasKey( 'objects', $results ); + $this->assertArrayHasKey( 'total_objects', $results ); + $this->assertEquals( 2, $results['total_objects'] ); } /**