Skip to content

Rewrite Rules: Add opt-in support for random content redirects. - #13022

Open
mlaetitia wants to merge 3 commits into
WordPress:trunkfrom
mlaetitia:trac-64498-random-content-redirect
Open

Rewrite Rules: Add opt-in support for random content redirects.#13022
mlaetitia wants to merge 3 commits into
WordPress:trunkfrom
mlaetitia:trac-64498-random-content-redirect

Conversation

@mlaetitia

@mlaetitia mlaetitia commented Aug 12, 2026

Copy link
Copy Markdown

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

This PR adds a minimal, opt-in implementation of random content redirects

What it does

  • Adds wp_random_content_redirect() (in wp-includes/query.php, alongside its closest sibling wp_old_slug_redirect()), hooked on template_redirect.

  • When enabled, a request to example.com/?random issues a 302 redirect to a randomly selected published post. ?random&random_post_type=page restricts the pick to a post type (viewable types only); ?random&random_cat_id=<term ID> restricts it to a category. These parameter names match Matt's original Random Redirect plugin referenced in the ticket, so existing ?random links keep working.

  • Disabled by default — per the ticket's request for an opt-in implementation with no user-facing settings or defaults, nothing happens unless a theme or plugin opts in:

    add_filter( 'enable_random_content_redirect', '__return_true' );
  • A second filter, random_content_redirect_url, allows short-circuiting or rewriting the redirect target (same pattern as old_slug_redirect_url).

Implementation notes

  • The random pick uses a COUNT plus a random OFFSET (two WP_Query calls) rather than ORDER BY RAND(), which randomizes and sorts every candidate row on each request and is uncached.
  • Password-protected and non-published posts are never redirect targets. Non-viewable post types are rejected. Only GET/HEAD requests are handled.
  • The redirect is a 302 so clients do not cache the randomly picked target.
  • Open question for review: the parameters are read from $_GET on template_redirect, matching the original plugin's addressing. If pretty-URL support (e.g. a /random/ rewrite endpoint) is desirable, the parameters should instead become registered query vars — happy to rework in that direction if preferred.

Tests

tests/phpunit/tests/rewrite/randomContentRedirect.php covers: disabled-by-default behavior, no-op without the random parameter, redirect to a published post, post type and category restriction, exclusion of password-protected/draft/private posts, invalid and non-viewable post types, nonexistent categories, empty result sets, and non-GET requests.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Fable 5
Used for: Initial implementation and test suite, ported from the Jetpack Random Redirect module under my direction; reviewed, tested, and edited by me.


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.

mlaetitia and others added 2 commits August 12, 2026 17:24
Adds wp_random_content_redirect(), hooked on template_redirect, which
redirects ?random requests to a randomly selected published post.
Disabled by default; enable with the enable_random_content_redirect
filter. Picks via COUNT plus a random OFFSET instead of ORDER BY RAND().

See https://core.trac.wordpress.org/ticket/64498

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Check for the random parameter before evaluating the enable filter so
disabled sites pay no filter lookup per request. Validate post type
viewability unconditionally, prime the post cache for get_permalink(),
skip the second query when the random offset is zero, and drop test
teardown that the test framework already guarantees.

See https://core.trac.wordpress.org/ticket/64498

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 12, 2026

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.

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

Props mlaetitia, ellatrix, mcsf.

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

@github-actions

Copy link
Copy Markdown

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

  • 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.

@ellatrix ellatrix left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just some nits, leaving some time for others to see this and can commit if you're ready.

Comment thread src/wp-includes/query.php
}

// Temporary redirect, so clients do not cache the randomly picked target.
wp_safe_redirect( $link, 302 );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we also call nocache_headers?

@mcsf mcsf Aug 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I suppose the intent here is to not cache the redirection (hence 302), while allowing clients to cache the resolved page (i.e. the randomly selected post). No?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wouldn't it cache based on the URL? I'm not a page cache expert, hence my comment being formatted as a question 🙂

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks both for the comments. I ended up adding a nocache_headers as we actually want both as much as possible:

  • the 302 makes the redirect non-permanent
  • stop intermediary caches (CDNs, full-page caches) from pinning the redirect, which would make ?random return the same post for everyone

Thanks! Hadn't considered this properly

Comment thread src/wp-includes/query.php
if ( isset( $_GET['random_cat_id'] ) ) {
$cat_id = absint( wp_unslash( $_GET['random_cat_id'] ) );

if ( $cat_id < 1 ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

absint always returns positive? Should it be (int) instead maybe?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Great catch! I changed this and also on the same note changed the test that checked the negative value as it was generally failing (as it should) but mostly for the wrong reasons.
changed both on the last commit

Comment thread tests/phpunit/tests/rewrite/randomContentRedirect.php
Comment thread tests/phpunit/tests/rewrite/randomContentRedirect.php
Comment thread src/wp-includes/query.php Outdated
return;
}

if ( isset( $_SERVER['REQUEST_METHOD'] ) && ! in_array( strtoupper( $_SERVER['REQUEST_METHOD'] ), array( 'GET', 'HEAD' ), true ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if by design, but if $_SERVER['REQUEST_METHOD'] isn't set, it will not return early.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When would it not be set?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Generally it wouldn't (maybe some CLI scenarios?) , but I rewrote this to explicitly accept only GET or HEAD.
Note: I didn't include the new QUERY method since Core doesn't yet support it (so can't imagine a scenario where it would be sent)

Comment thread tests/phpunit/tests/rewrite/randomContentRedirect.php Outdated
Comment thread tests/phpunit/tests/rewrite/randomContentRedirect.php
Comment thread tests/phpunit/tests/rewrite/randomContentRedirect.php
Comment thread tests/phpunit/tests/rewrite/randomContentRedirect.php
@mlaetitia
mlaetitia requested a review from mcsf August 13, 2026 18:06
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.

3 participants