Skip to content

Conversation

@ramonjd
Copy link
Member

@ramonjd ramonjd commented Dec 12, 2025

Follow up to #10394

Tests out an idea suggested in https://core.trac.wordpress.org/ticket/64390#comment:10 Props @azaozz

Note

This is mainly to discuss whether it's worth the change, if so good, if not, I can close.

Description

This PR is a follow-up to #10394 that makes the deterministic ordering changes more defensive and plugin-friendly. It ensures that when plugins modify the get_{$adjacent}_post_where or get_{$adjacent}_post_sort filters, the deterministic logic is not applied on top of their modifications.

Problem

In PR #10394, deterministic ID-based ordering was added to fix adjacent post navigation for posts with identical dates. However, the implementation applied the deterministic logic before the filters ran, which meant:

  1. Plugins modifying the WHERE or SORT clauses via filters would receive SQL that already included the deterministic changes
  2. Plugins couldn't opt out or modify the SQL in ways that might conflict with the deterministic logic
  3. This could break plugins that parse or manipulate the SQL clauses

Solution

This PR implements a defensive approach that:

  1. Applies filters first with the original (non-deterministic) SQL clauses
  2. Only applies deterministic logic if filters don't modify the clauses - checks if the filtered value equals the original prepared value
  3. Respects plugin modifications - when filters modify the SQL, their changes are preserved and deterministic logic is not applied

Implementation:

  • For WHERE clause: Only applies ID-based fallback if $where === $where_prepared (filter didn't modify it)
  • For SORT clause: Only applies ID-based sorting if $sort === $sort_prepared (filter didn't modify it)

Test Steps

Automated Tests

Run the PHPUnit tests:

npm run test:php -- --filter Tests_Link_GetAdjacentPost

Manual Testing

1. Verify Default Behavior (No Filters)

  1. Create test scenario:

    • Create multiple posts with identical post_date values (bulk publish drafts)
    • Navigate between posts
  2. Expected Results:

    • Navigation works deterministically, you should be able to navigate through all posts with identical dates

2. Verify Filter Modifications Are Respected

Test WHERE Filter:

  1. Add test filter to functions.php or a test plugin:
// Test filter for next post WHERE clause
add_filter( 'get_next_post_where', function( $where ) {
	// Modify the WHERE clause - deterministic fallback should NOT be applied.
	return $where . ' AND 1=1';
}, 10, 1 );

// Test filter for previous post WHERE clause
add_filter( 'get_previous_post_where', function( $where ) {
	// Modify the WHERE clause - deterministic fallback should NOT be applied.
	return $where . ' AND 1=1';
}, 10, 1 );
  1. Verify behavior:
    • Navigate between posts, rhe deterministic ID fallback should NOT be applied (no AND p.ID in WHERE clause)

Test SORT Filter:

  1. Remove WHERE filters, add SORT filter:
add_filter( 'get_next_post_sort', function( $sort, $post, $order ) {
	// Remove ID from sort - deterministic ID sort should NOT be applied.
	return "ORDER BY p.post_date $order LIMIT 1";
}, 10, 3 );

add_filter( 'get_previous_post_sort', function( $sort, $post, $order ) {
	return "ORDER BY p.post_date $order LIMIT 1";
}, 10, 3 );
  1. Verify behavior:
    • Create posts with identical dates
    • Navigate between posts, deterministic ID sort is NOT applied

3. Verify Unmodified Filters Still Get Deterministic Logic

  1. Add filters that don't modify the clauses:
add_filter( 'get_next_post_where', function( $where ) {
	// Return unchanged - deterministic fallback should be applied.
	return $where;
}, 10, 1 );

add_filter( 'get_next_post_sort', function( $sort ) {
	// Return unchanged - deterministic ID sort should be applied.
	return $sort;
}, 10, 1 );
  1. Verify behavior:
    • Navigation works deterministically, you should be able to navigate through all posts with identical dates

Trac ticket: https://core.trac.wordpress.org/ticket/64390

…ery hasn't been modified.

This update introduces a deterministic fallback for the SQL `WHERE` and `ORDER BY` clauses in the `get_adjacent_post` function when posts have identical dates. The fallback is applied only if the respective clauses have not been modified by filters, ensuring consistent behavior.

Unit tests have been added to verify the correct application of this fallback under various conditions, including scenarios where filters are applied or not.

See Trac ticket https://core.trac.wordpress.org/ticket/64390.
@github-actions
Copy link

github-actions bot commented Dec 12, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props ramonopoly, peterwilsoncc.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a few notes inline.

Comment on lines +661 to +662

remove_all_filters( 'get_next_post_where' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test suite does this automatically so it can be removed.

🔢 Applies in the tests below so I won't repeat myself.

Suggested change
remove_all_filters( 'get_next_post_where' );

// Next post should be the 4th post (higher ID, same date) - deterministic.
$next = get_adjacent_post( false, '', false );
$this->assertInstanceOf( 'WP_Post', $next );
$this->assertEquals( $post_ids[3], $next->ID );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->assertEquals( $post_ids[3], $next->ID );
$this->assertSame( $post_ids[3], $next->ID );

🔢 Looks to apply elsewhere so I won't repeat myself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻


// Only force deterministic fallback if the where clause has not been modified by a filter.
if ( $where === $where_prepared ) {
$where = $where_prepared_with_deterministic_fallback;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason you're defining $where_prepared_with_deterministic_fallback above rather than here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to reduce my carbon footprint by limiting my Ctrl+V and Ctrl+P :trollface:

No reason at all actually. I can move.

* @param string $order Sort order. 'DESC' for previous post, 'ASC' for next.
*/
$sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order, p.ID $order LIMIT 1", $post, $order );
$sort_prepared = "ORDER BY p.post_date $order LIMIT 1";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be done above the docblock so docs-parsers can figure out the filter it applies to.

* @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
* @since 6.9.0 Adds ID-based fallback for posts with identical dates in adjacent post queries.
*
* @param string $where The `WHERE` clause in the SQL.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yours truly missed this last time.

Suggested change
* @param string $where The `WHERE` clause in the SQL.
* @param string $where_prepared The `WHERE` clause in the SQL.

* Can either be next or previous post.
*
* @since 2.5.0
* @since 6.9.1 Adds deterministic fallback for sort clause if not modified by a filter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably include the since changes for 6.0.0 that yours truly also missed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are they? 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this?

Suggested change
* @since 6.9.1 Adds deterministic fallback for sort clause if not modified by a filter.
* @since 6.9.0 Introduce deterministic fallback based in IDs to account for date collisions.
* @since 6.9.1 Remove deterministic fallback for sites modifying the WHERE clause via a filter.
* See https://core.trac.wordpress.org/ticket/64390

* @since 2.5.0
* @since 4.4.0 Added the `$post` parameter.
* @since 4.9.0 Added the `$order` parameter.
* @since 6.9.0 Adds ID sort to ensure deterministic ordering for posts with identical dates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it best to keep this and then not that it was removed in 6.9.1 and applied later if changed by a filter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah great thanks, I was wondering about that. Cheers

* Can either be next or previous post.
*
* @since 2.5.0
* @since 6.9.1 Adds deterministic fallback for sort clause if not modified by a filter.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure whether to also add a 6.9.0 entry for historical purposes

*
* @since 2.5.0
* @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
* @since 6.9.0 Adds ID-based fallback for posts with identical dates in adjacent post queries.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This okay to remove, or should we have some ref to historical changes?

@ramonjd
Copy link
Member Author

ramonjd commented Dec 12, 2025

Thanks @peterwilsoncc I'll fix those things up next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants