Skip to content

Commit 020d4e7

Browse files
committed
Enhancement: Add ID-based fallback for adjacent post queries with identical dates.
This update modifies the `get_adjacent_post` function to ensure deterministic ordering when multiple posts share the same date by incorporating the post ID in the SQL query. Additionally, new unit tests have been added to verify the functionality for posts with identical dates, ensuring correct navigation through adjacent posts.
1 parent 33d4edd commit 020d4e7

File tree

3 files changed

+261
-2
lines changed

3 files changed

+261
-2
lines changed

src/wp-includes/link-template.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,14 +1977,15 @@ function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previo
19771977
*
19781978
* @since 2.5.0
19791979
* @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
1980+
* @since n.e.x.t Adds ID-based fallback for posts with identical dates in adjacent post queries.
19801981
*
19811982
* @param string $where The `WHERE` clause in the SQL.
19821983
* @param bool $in_same_term Whether post should be in the same taxonomy term.
19831984
* @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
19841985
* @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
19851986
* @param WP_Post $post WP_Post object.
19861987
*/
1987-
$where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare( "WHERE p.post_date $op %s AND p.post_type = %s $where", $current_post_date, $post->post_type ), $in_same_term, $excluded_terms, $taxonomy, $post );
1988+
$where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare( "WHERE (p.post_date $op %s OR (p.post_date = %s AND p.ID $op %d)) AND p.post_type = %s $where", $current_post_date, $current_post_date, $post->ID, $post->post_type ), $in_same_term, $excluded_terms, $taxonomy, $post );
19881989

19891990
/**
19901991
* Filters the ORDER BY clause in the SQL for an adjacent post query.
@@ -2000,12 +2001,13 @@ function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previo
20002001
* @since 2.5.0
20012002
* @since 4.4.0 Added the `$post` parameter.
20022003
* @since 4.9.0 Added the `$order` parameter.
2004+
* @since n.e.x.t Adds ID sort to ensure deterministic ordering for posts with identical dates.
20032005
*
20042006
* @param string $order_by The `ORDER BY` clause in the SQL.
20052007
* @param WP_Post $post WP_Post object.
20062008
* @param string $order Sort order. 'DESC' for previous post, 'ASC' for next.
20072009
*/
2008-
$sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1", $post, $order );
2010+
$sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order, p.ID $order LIMIT 1", $post, $order );
20092011

20102012
$query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
20112013
$key = md5( $query );

tests/phpunit/tests/link/getAdjacentPost.php

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,4 +587,196 @@ public function test_get_adjacent_post_cache() {
587587
$this->assertEquals( $post_four, get_adjacent_post( true, '', false ), 'Result of function call is wrong after after adding new term' );
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
}

tests/phpunit/tests/url.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,4 +569,69 @@ public function test_url_functions_for_dots_in_paths() {
569569
);
570570
}
571571
}
572+
573+
/**
574+
* Test get_adjacent_post with posts having identical post_date.
575+
*
576+
* @ticket 8107
577+
* @covers ::get_adjacent_post
578+
*/
579+
public function test_get_adjacent_post_with_identical_dates() {
580+
$identical_date = gmdate( 'Y-m-d H:i:s', time() );
581+
582+
// Create 3 posts with identical dates but different IDs
583+
$post_ids = array();
584+
for ( $i = 1; $i <= 3; $i++ ) {
585+
$post_ids[] = self::factory()->post->create(
586+
array(
587+
'post_title' => "Identical Post $i",
588+
'post_date' => $identical_date,
589+
)
590+
);
591+
}
592+
593+
if ( ! isset( $GLOBALS['post'] ) ) {
594+
$GLOBALS['post'] = null;
595+
}
596+
$orig_post = $GLOBALS['post'];
597+
598+
// Test from the middle post (2nd post)
599+
$GLOBALS['post'] = get_post( $post_ids[1] );
600+
601+
// Previous post should be the 1st post (lower ID, same date)
602+
$previous = get_adjacent_post( false, '', true );
603+
$this->assertInstanceOf( 'WP_Post', $previous );
604+
$this->assertSame( $post_ids[0], $previous->ID );
605+
606+
// Next post should be the 3rd post (higher ID, same date)
607+
$next = get_adjacent_post( false, '', false );
608+
$this->assertInstanceOf( 'WP_Post', $next );
609+
$this->assertSame( $post_ids[2], $next->ID );
610+
611+
// Test from the first post
612+
$GLOBALS['post'] = get_post( $post_ids[0] );
613+
614+
// Previous should be empty (no earlier posts)
615+
$previous = get_adjacent_post( false, '', true );
616+
$this->assertSame( '', $previous );
617+
618+
// Next should be the 2nd post
619+
$next = get_adjacent_post( false, '', false );
620+
$this->assertInstanceOf( 'WP_Post', $next );
621+
$this->assertSame( $post_ids[1], $next->ID );
622+
623+
// Test from the last post
624+
$GLOBALS['post'] = get_post( $post_ids[2] );
625+
626+
// Previous should be the 2nd post
627+
$previous = get_adjacent_post( false, '', true );
628+
$this->assertInstanceOf( 'WP_Post', $previous );
629+
$this->assertSame( $post_ids[1], $previous->ID );
630+
631+
// Next should be empty (no later posts)
632+
$next = get_adjacent_post( false, '', false );
633+
$this->assertSame( '', $next );
634+
635+
$GLOBALS['post'] = $orig_post;
636+
}
572637
}

0 commit comments

Comments
 (0)