Skip to content

#35082 - Add a get_{$adjacent}_post_terms filter - #13591

Open
skikken wants to merge 2 commits into
WordPress:trunkfrom
skikken:fix/35082-adjacent-post-terms-filter
Open

skikken wants to merge 2 commits into
WordPress:trunkfrom
skikken:fix/35082-adjacent-post-terms-filter

Conversation

@skikken

@skikken skikken commented Sep 17, 2026

Copy link
Copy Markdown

The reporter wants get_adjacent_post() with $in_same_term to only consider leaf terms, so that two posts sharing only a parent term are not treated as adjacent when their deepest terms differ.

This PR deliberately does not do that. That behaviour change was reviewed and rejected on the ticket:

"My reading of the parameter in_same_term is that it should return the next/prev post that shares any term with the current post. Your suggested change is that only leaf terms should be matched. This is a pretty big change in functionality, and I think it's not desirable for many use cases. Changing the behavior of in_same_term to mean this is, I think, not doable."

The maintainer then offered an alternative and the reporter agreed to pursue it:

"we might consider a new filter that would allow this behavior to be modified in a non-insane way (ie, a technique that does not require filtering the entire SQL string)."

"I'll have a look at creating a patch with a filter approach so that more can be manipulated with this."

That filter was never written. This PR adds it.

The problem with the status quo is that the term list is resolved in PHP and then interpolated into the SQL, so the only way to change which terms are matched is to filter the finished SQL string. The new get_{$adjacent}_post_terms filter exposes the resolved term IDs instead:

$term_array = array_diff( $term_array, (array) $excluded_terms );

/** This filter is documented in wp-includes/link-template.php */
$term_array = apply_filters( "get_{$adjacent}_post_terms", $term_array, $post, $taxonomy, $in_same_term, $excluded_terms );

if ( ! $term_array ) {
    return '';
}

$term_array = array_map( 'intval', $term_array );

Three placement decisions:

  • The filter is after the $excluded_terms diff, so the callback sees the list that will actually be used rather than one it then has to reconcile against exclusions.
  • It is before the empty-array guard, so a callback that returns an empty array gets the existing "no adjacent post" result rather than an empty IN () clause, which would be invalid SQL.
  • It is before array_map( 'intval', ... ). That ordering matters: term IDs are interpolated directly into the query, so keeping the integer cast downstream of the filter means a filtered value cannot reach SQL without being cast. An earlier position would have made the filter an injection surface.

The name and argument style follow the existing dynamic filters in the same function (get_{$adjacent}_post_excluded_terms, get_{$adjacent}_post_join, get_{$adjacent}_post_where, get_{$adjacent}_post_sort), so both get_previous_post_terms and get_next_post_terms are available.

Nothing changes by default. $in_same_term still matches any shared term, and there is a test pinning that so the rejected behaviour cannot creep in.

The main test builds the reporter's own fixture, a parent term with a child term and three posts, and asserts both halves:

  • unfiltered, the adjacent post is the one sharing the parent term (current behaviour, preserved)
  • with a callback narrowing the term array to the child term, the adjacent post becomes the one sharing the child term (what the reporter wanted, now achievable)

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

Use of AI Tools

AI assistance: Yes
Tool(s): Claude
Model(s): Sonnet
Used for: test cases, code review


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

get_adjacent_post() resolved the terms used for the in_same_term clause in
PHP and interpolated them straight into SQL, so the only way to change which
terms are matched was to filter the finished SQL string.

Add a dynamic get_{$adjacent}_post_terms filter on the resolved term array,
placed after the excluded terms are removed and before the term IDs are cast
to integers, so a filtered value is still cast before it reaches the query.

This lets a site narrow the matched terms, for example to restrict matching
to a child term, without touching the generated SQL. The default behaviour is
unchanged: the callback still matches a post that shares any term, and a
filter that returns an empty array results in the existing no-adjacent-post
behaviour rather than an empty IN clause.

Props WazzaJB, boonebgorges.
Fixes #35082.
Assert that the adjacent post terms filter is applied, that it receives the
documented arguments, and that narrowing the term array to a child term
changes the adjacent post from one that shares only the parent term to one
that shares the child term.

Also pins the default behaviour, so the existing in_same_term semantics of
matching any shared term are proven to be preserved, and covers a filter
that removes every term.

Fixes #35082.
@github-actions

Copy link
Copy Markdown

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.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @skikken.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant