Make WordPress Core


Ignore:
Timestamp:
10/26/2025 09:19:07 PM (6 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.