Make WordPress Core


Ignore:
Timestamp:
10/26/2025 09:19:07 PM (11 months ago)
Author:
ramonopoly
Message:

get_adjacent_post: modify WHERE clause to include ID-based fallback to ensure deterministic ordering

Since WordPress 2.7, where multiple posts have identical post_date values (e.g., when bulk publishing drafts), the next/previous post navigation skips posts or behaves unpredictably. This is because the WHERE clause uses strict inequality (> or <) which excludes posts with the same date.

To ensure deterministic ordering, this commit modifies the WHERE clause to include ID-based fallback for posts with identical dates.

Props ramonopoly, westonruter, andrewserong.

Fixes #8107.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/link/getAdjacentPost.php

    r60733 r61066  
    588588                $this->assertSame( get_num_queries() - $num_queries, 2, 'Number of queries run was not two after adding new term' );
    589589        }
     590
     591        /**
     592         * Test get_adjacent_post with posts having identical post_date.
     593         *
     594         * @ticket 8107
     595         */
     596        public function test_get_adjacent_post_with_identical_dates() {
     597                $identical_date = '2024-01-01 12:00:00';
     598
     599                // Create posts with identical dates but different IDs.
     600                $post_ids = array();
     601                for ( $i = 1; $i <= 5; $i++ ) {
     602                        $post_ids[] = self::factory()->post->create(
     603                                array(
     604                                        'post_title' => "Post $i",
     605                                        'post_date'  => $identical_date,
     606                                )
     607                        );
     608                }
     609
     610                // Test navigation from the middle post (ID: 3rd post).
     611                $current_post_id = $post_ids[2]; // 3rd post
     612                $this->go_to( get_permalink( $current_post_id ) );
     613
     614                // Previous post should be the 2nd post (lower ID, same date).
     615                $previous = get_adjacent_post( false, '', true );
     616                $this->assertInstanceOf( 'WP_Post', $previous );
     617                $this->assertEquals( $post_ids[1], $previous->ID );
     618
     619                // Next post should be the 4th post (higher ID, same date).
     620                $next = get_adjacent_post( false, '', false );
     621                $this->assertInstanceOf( 'WP_Post', $next );
     622                $this->assertEquals( $post_ids[3], $next->ID );
     623        }
     624
     625        /**
     626         * Test get_adjacent_post with mixed dates and identical dates.
     627         *
     628         * @ticket 8107
     629         */
     630        public function test_get_adjacent_post_mixed_dates_with_identical_groups() {
     631                // Create posts with different dates.
     632                $post_early = self::factory()->post->create(
     633                        array(
     634                                'post_title' => 'Early Post',
     635                                'post_date'  => '2024-01-01 10:00:00',
     636                        )
     637                );
     638
     639                // Create multiple posts with identical date.
     640                $identical_date = '2024-01-01 12:00:00';
     641                $post_ids       = array();
     642                for ( $i = 1; $i <= 3; $i++ ) {
     643                        $post_ids[] = self::factory()->post->create(
     644                                array(
     645                                        'post_title' => "Identical Post $i",
     646                                        'post_date'  => $identical_date,
     647                                )
     648                        );
     649                }
     650
     651                $post_late = self::factory()->post->create(
     652                        array(
     653                                'post_title' => 'Late Post',
     654                                'post_date'  => '2024-01-01 14:00:00',
     655                        )
     656                );
     657
     658                // Test from first identical post.
     659                $this->go_to( get_permalink( $post_ids[0] ) );
     660
     661                // Previous should be the early post (different date).
     662                $previous = get_adjacent_post( false, '', true );
     663                $this->assertInstanceOf( 'WP_Post', $previous );
     664                $this->assertEquals( $post_early, $previous->ID );
     665
     666                // Next should be the second identical post (same date, higher ID).
     667                $next = get_adjacent_post( false, '', false );
     668                $this->assertInstanceOf( 'WP_Post', $next );
     669                $this->assertEquals( $post_ids[1], $next->ID );
     670
     671                // Test from middle identical post.
     672                $this->go_to( get_permalink( $post_ids[1] ) );
     673
     674                // Previous should be the first identical post (same date, lower ID).
     675                $previous = get_adjacent_post( false, '', true );
     676                $this->assertInstanceOf( 'WP_Post', $previous );
     677                $this->assertEquals( $post_ids[0], $previous->ID );
     678
     679                // Next should be the third identical post (same date, higher ID).
     680                $next = get_adjacent_post( false, '', false );
     681                $this->assertInstanceOf( 'WP_Post', $next );
     682                $this->assertEquals( $post_ids[2], $next->ID );
     683
     684                // Test from last identical post.
     685                $this->go_to( get_permalink( $post_ids[2] ) );
     686
     687                // Previous should be the second identical post (same date, lower ID).
     688                $previous = get_adjacent_post( false, '', true );
     689                $this->assertInstanceOf( 'WP_Post', $previous );
     690                $this->assertEquals( $post_ids[1], $previous->ID );
     691
     692                // Next should be the late post (different date).
     693                $next = get_adjacent_post( false, '', false );
     694                $this->assertInstanceOf( 'WP_Post', $next );
     695                $this->assertEquals( $post_late, $next->ID );
     696        }
     697
     698        /**
     699         * Test get_adjacent_post navigation through all posts with identical dates.
     700         *
     701         * @ticket 8107
     702         */
     703        public function test_get_adjacent_post_navigation_through_identical_dates() {
     704                $identical_date = '2024-01-01 12:00:00';
     705
     706                // Create 4 posts with identical dates.
     707                $post_ids = array();
     708                for ( $i = 1; $i <= 4; $i++ ) {
     709                        $post_ids[] = self::factory()->post->create(
     710                                array(
     711                                        'post_title' => "Post $i",
     712                                        'post_date'  => $identical_date,
     713                                )
     714                        );
     715                }
     716
     717                // Test navigation sequence: 1 -> 2 -> 3 -> 4.
     718                $this->go_to( get_permalink( $post_ids[0] ) );
     719
     720                // From post 1, next should be post 2.
     721                $next = get_adjacent_post( false, '', false );
     722                $this->assertEquals( $post_ids[1], $next->ID );
     723
     724                // From post 2, previous should be post 1, next should be post 3.
     725                $this->go_to( get_permalink( $post_ids[1] ) );
     726                $previous = get_adjacent_post( false, '', true );
     727                $this->assertEquals( $post_ids[0], $previous->ID );
     728                $next = get_adjacent_post( false, '', false );
     729                $this->assertEquals( $post_ids[2], $next->ID );
     730
     731                // From post 3, previous should be post 2, next should be post 4.
     732                $this->go_to( get_permalink( $post_ids[2] ) );
     733                $previous = get_adjacent_post( false, '', true );
     734                $this->assertEquals( $post_ids[1], $previous->ID );
     735                $next = get_adjacent_post( false, '', false );
     736                $this->assertEquals( $post_ids[3], $next->ID );
     737
     738                // From post 4, previous should be post 3.
     739                $this->go_to( get_permalink( $post_ids[3] ) );
     740                $previous = get_adjacent_post( false, '', true );
     741                $this->assertEquals( $post_ids[2], $previous->ID );
     742        }
     743
     744        /**
     745         * Test get_adjacent_post with identical dates and category filtering.
     746         *
     747         * @ticket 8107
     748         */
     749        public function test_get_adjacent_post_identical_dates_with_category() {
     750                $identical_date = '2024-01-01 12:00:00';
     751                $category_id    = self::factory()->category->create( array( 'name' => 'Test Category' ) );
     752
     753                // Create posts with identical dates, some in category.
     754                $post_ids = array();
     755                for ( $i = 1; $i <= 4; $i++ ) {
     756                        $post_id = self::factory()->post->create(
     757                                array(
     758                                        'post_title' => "Post $i",
     759                                        'post_date'  => $identical_date,
     760                                )
     761                        );
     762
     763                        // Add every other post to the category.
     764                        if ( 0 === $i % 2 ) {
     765                                wp_set_post_categories( $post_id, array( $category_id ) );
     766                        }
     767
     768                        $post_ids[] = $post_id;
     769                }
     770
     771                // Test from post 2 (in category).
     772                $this->go_to( get_permalink( $post_ids[1] ) );
     773
     774                // With category filtering, should only see posts in same category.
     775                $previous = get_adjacent_post( true, '', true, 'category' );
     776                $this->assertSame( '', $previous ); // No previous post in category
     777
     778                $next = get_adjacent_post( true, '', false, 'category' );
     779                $this->assertInstanceOf( 'WP_Post', $next );
     780                $this->assertEquals( $post_ids[3], $next->ID ); // Post 4 (in category)
     781        }
    590782}
Note: See TracChangeset for help on using the changeset viewer.