Skip to content

Add randomness for post selection - #14018

Closed
aaronjorbin wants to merge 18 commits into
WordPress:trunkfrom
aaronjorbin:trac/64498
Closed

aaronjorbin wants to merge 18 commits into
WordPress:trunkfrom
aaronjorbin:trac/64498

Conversation

@aaronjorbin

Copy link
Copy Markdown
Member

Random content redirects are built on the existing rewrite and query layers. random is a public query var, so ?random works on any archive, search or home URL. WP_Rewrite::random_rewrite_rules(), prefixed by the new $random_base, uses generate_rewrite_rules() to build /random/, /random/{taxonomy}/{term}/, /random/author/{name}/ and /random/{post-type-archive}/. These rules are merged ahead of the greedy page and post rules. Which content can be picked is set by a new randomable post type argument, which falls back to publicly_queryable and then to public. Posts are randomable; pages and attachments are not. Rules are only generated for randomable types and for taxonomies attached to them. A published page at the random base takes precedence over /random/. That check happens when the rules are generated, and wp_after_insert_post and after_delete_post flush the rules whenever such a page changes. The trade-off is that request time stays free of the extra lookup, at the cost of a rule flush whenever that one page changes. It also makes the rules depend on content, so a stale cache could briefly show the wrong winner. Separately, rules for a disabled feature or a newly registered type only appear after a flush, which matches how the rest of the rewrite API behaves.

The main query does the random pick. A late pre_get_posts callback at priority 1000 sets one row, orderby=rand, published and non-password posts, no stickies, no found rows and no cache priming. It also neutralizes the overrides themes commonly set: nopaging, posts_per_archive_page, showposts and offset. A split_the_query callback stops WP_Query from splitting the request, because random queries are never cached. The result is that /random/ costs exactly one database query, and nothing is queried before it. Every WP_Query filter still applies, which was Peter's main objection to the reverted version. The late priority protects randomness from theme archive tweaks. The cost is that developers who want to change the selection must use wp_is_random_content_query() inside pre_get_posts, and they can't widen eligibility past randomable. ORDER BY RAND() is a full scan of the eligible rows. It was accepted over a COUNT plus random OFFSET, which takes two queries, because one query was the stated goal; very large sites may eventually want a cheaper strategy behind a filter. The redirect itself is a 302 sent on send_headers, before the wp action and template loading, with no-cache and noindex headers added through wp_headers. Singular, feed, admin, non-GET/HEAD and empty-archive requests fall through to normal rendering. A permalink on a host wp_safe_redirect() won't allow is skipped instead of being replaced by its dashboard fallback. That favors safety over following plugins that point permalinks to other domains.

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

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Opus 5.5
Used for: basically everything


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.

Random content redirects are built on the existing rewrite and query layers. random is a public query var, so ?random works on any archive, search or home URL. WP_Rewrite::random_rewrite_rules(), prefixed by the new $random_base, uses generate_rewrite_rules() to build /random/, /random/{taxonomy}/{term}/, /random/author/{name}/ and /random/{post-type-archive}/. These rules are merged ahead of the greedy page and post rules. Which content can be picked is set by a new randomable post type argument, which falls back to publicly_queryable and then to public. Posts are randomable; pages and attachments are not. Rules are only generated for randomable types and for taxonomies attached to them. A published page at the random base takes precedence over /random/. That check happens when the rules are generated, and wp_after_insert_post and after_delete_post flush the rules whenever such a page changes. The trade-off is that request time stays free of the extra lookup, at the cost of a rule flush whenever that one page changes. It also makes the rules depend on content, so a stale cache could briefly show the wrong winner. Separately, rules for a disabled feature or a newly registered type only appear after a flush, which matches how the rest of the rewrite API behaves.

The main query does the random pick. A late pre_get_posts callback at priority 1000 sets one row, orderby=rand, published and non-password posts, no stickies, no found rows and no cache priming. It also neutralizes the overrides themes commonly set: nopaging, posts_per_archive_page, showposts and offset. A split_the_query callback stops WP_Query from splitting the request, because random queries are never cached. The result is that /random/ costs exactly one database query, and nothing is queried before it. Every WP_Query filter still applies, which was Peter's main objection to the reverted version. The late priority protects randomness from theme archive tweaks. The cost is that developers who want to change the selection must use wp_is_random_content_query() inside pre_get_posts, and they can't widen eligibility past randomable. ORDER BY RAND() is a full scan of the eligible rows. It was accepted over a COUNT plus random OFFSET, which takes two queries, because one query was the stated goal; very large sites may eventually want a cheaper strategy behind a filter. The redirect itself is a 302 sent on send_headers, before the wp action and template loading, with no-cache and noindex headers added through wp_headers. Singular, feed, admin, non-GET/HEAD and empty-archive requests fall through to normal rendering. A permalink on a host wp_safe_redirect() won't allow is skipped instead of being replaced by its dashboard fallback. That favors safety over following plugins that point permalinks to other domains.

Trac Ticket: https://core.trac.wordpress.org/ticket/64498
@github-actions

github-actions Bot commented Oct 5, 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 jorbin, peterwilsoncc, westonruter, mukesh27, annezazu.

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

@github-actions

github-actions Bot commented Oct 5, 2026

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.

Comment thread src/wp-includes/query.php Outdated
Parameter #2 $fallback_url of function wp_validate_redirect expects string, false given.
@annezazu

annezazu commented Oct 5, 2026

Copy link
Copy Markdown

Gave this a spin locally and it's working well! I used AI to put in some filler content.

Testing with just /random:

Screen.Recording.2026-10-05.at.12.39.51.PM.mov

Testing with /random/author/[name]:

Screen.Recording.2026-10-05.at.12.40.31.PM.mov

I also tested with an author without any posts and it showed no results:

Screen.Recording.2026-10-05.at.12.42.51.PM.mov

@peterwilsoncc peterwilsoncc left a comment

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've done a first pass review and left a few comments inline.

This is looking pretty good generally.

Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/query.php
Comment thread src/wp-includes/class-wp-post-type.php Outdated
$args['embeddable'] ??= $args['public'];

// If not set, default to the setting for 'publicly_queryable'.
$args['randomable'] ??= $args['publicly_queryable'];

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.

&& has_archive ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I used publicly_queryable as the default since I felt this was a logical extension. As this is getting a single item, I'm not sure I see the same connection to has_archive

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.

My thinking here is that the condition in the rewrite loop only adds the rewrite if the post type has an archive and a rewrite slug.

https://github.com/WordPress/wordpress-develop/pull/14018/changes#diff-9cf1519e03e09d5698082a4594d70539d9ce18aa16defbbae15a982583dec4efR1596-R1604

Comment thread src/wp-includes/class-wp-rewrite.php Outdated
* @since 7.2.0
* @var string
*/
public $random_base = 'random';

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.

This will need to be an option on the permalink screen for i18n: French users may want to change this to aléatoire, for example.

I can't find if the default values are translatable for the category and tag bases but it will be good to check in with the polyglot team. If the other bases aren't translatable it would be good to check with the team if that was a backward compatibility choice at the time to avoid breaking URLs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Category and tag bases not translatable. I'll check in Polyglots on if continuing this is desirable. I also am adding this to option to the permalink options page.

$rewrite = array(
'category' => array(
'hierarchical' => true,
'slug' => get_option( 'category_base' ) ? get_option( 'category_base' ) : 'category',
'with_front' => ! get_option( 'category_base' ) || $wp_rewrite->using_index_permalinks(),
'ep_mask' => EP_CATEGORIES,
),
'post_tag' => array(
'hierarchical' => false,
'slug' => get_option( 'tag_base' ) ? get_option( 'tag_base' ) : 'tag',
'with_front' => ! get_option( 'tag_base' ) || $wp_rewrite->using_index_permalinks(),
'ep_mask' => EP_TAGS,
),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Polyglots check - https://wordpress.slack.com/archives/C02RP50LK/p1791315932840629

@tobifjellner responded and requested it be translated. Making that so.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Some implementation notes:

  • If the translation runs in populate_option, it will likely be inserted untranslated since language packs are downloaded after the core update.
  • Multilingual plugins are going to need to test this. Maybe worth a special email to committers of https://wordpress.org/plugins/tags/multilingual/ plugins?
  • if the translated string is updated outside of the WordPress upgrade, then the default "random" will get cached in the rewrite rules. We could flush the cache on updates to translated strings, but on multisite, that would mean needing to trigger an update that loops through every site since each has a unique cache.

add_filter( 'wp_redirect', array( $this, 'filter_wp_redirect' ), 10, 2 );
}

public function tear_down() {

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 think all of these are reset in the base tear down. Leaving confirmation as homework.

Post types certainly are.

array_merge(
array(
'post_status' => 'publish',
'post_date' => '2022-01-01 00:00:00',

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.

All I am saying is that:

Comment thread tests/phpunit/tests/query/randomContentRedirect.php Outdated
Comment thread tests/phpunit/tests/query/randomContentRedirect.php Outdated
Comment thread src/wp-includes/default-filters.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
@peterwilsoncc

Copy link
Copy Markdown
Contributor

i forgot to mention: the db version will need a bump in order to trigger the upgrade routine and a rewrite rules update.

Comment thread src/wp-includes/class-wp-rewrite.php Outdated
aaronjorbin and others added 2 commits October 7, 2026 13:26
Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Co-authored-by: Mukesh Panchal <mukeshpanchal27@users.noreply.github.com>
Settings and the random base
- New "Random base" field on Settings → Permalinks, beside Category base and Tag base. It only appears when random content redirects are enabled. The new random_base option is cleaned and saved the same way as the category and tag bases, including on multisite site settings. New WP_Rewrite::set_random_base(). New WP_Rewrite::get_random_base(). It returns the custom base if one is set; otherwise it translates the default (random) into the site's language when the rules are built. The $random_base property now holds only the custom value. No default random_base option is saved on install or upgrade. quest handling New WP::handle_random(), called from WP::main() in place of query_posts() for random requests. - It runs the random query as the main query, so theme and plugin pre_get_posts customisations still   apply. - While the query runs, it attaches callbacks at PHP_INT_MAX that enforce eligible posts   (pre_get_posts), random order (posts_orderby) and a limit of one (post_limits), then removes them.
  - It redirects to the selected post, sending no-cache and noindex headers.
  - When no post is selected, the request becomes a 404.
  - The main query runs only once.
- Off-site permalinks don't redirect: a selected post whose permalink is on a host that isn't allowed is shown in place, rather than sending the visitor to the wp_safe_redirect() fallback (/wp-admin/).
- The global pre_get_posts hook at priority 1000 and the send_headers redirect hook are removed.
- New is_random() and WP_Query::is_random(). The flag is set in parse_query() for random home, archive and search requests, and cleared by set_404(). wp_is_random_content_query() now uses it.

Conflicts with pages and posts
- A published post named random now takes precedence over /random/ when posts sit at the site root (/%postname%/ or /index.php/%postname%/), as pages already did.
- New WP_Rewrite::post_permalinks_share_random_base() checks for that root-level permalink setup.
- The flush function is renamed to wp_random_content_flush_rewrite_rules_for_post() and now also rebuilds the rules when a conflicting post changes.

Rewrite rule refreshes
- Changing the site language clears the stored rewrite rules, so they're rebuilt in the new language on the next request.
- $wp_db_version is bumped, so rules are rebuilt after upgrading. The number is a placeholder to set before committing.

Code quality
- The new code passes phpstan level 10, with tighter types: wp_random_content_headers() now allows false header values, and non-string post types and rewrite slugs are skipped.
- options-permalink.php now has a documented global $wp_rewrite, so its baselined phpstan entry is removed.

Tests
- Resets the base test class already performs are no longer repeated in the test classes.
- Every assertion has a unique message, apart from assertQueryTrue(), which can't take one.
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
* @since 7.2.0
* @var string
*/
public $random_base = 'random';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Some implementation notes:

  • If the translation runs in populate_option, it will likely be inserted untranslated since language packs are downloaded after the core update.
  • Multilingual plugins are going to need to test this. Maybe worth a special email to committers of https://wordpress.org/plugins/tags/multilingual/ plugins?
  • if the translated string is updated outside of the WordPress upgrade, then the default "random" will get cached in the rewrite rules. We could flush the cache on updates to translated strings, but on multisite, that would mean needing to trigger an update that loops through every site since each has a unique cache.

Comment thread src/wp-includes/default-filters.php Outdated
Comment thread tests/phpstan/baselines/variable.undefined.neon
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated

@peterwilsoncc peterwilsoncc left a comment

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.

Some quick night brain thoughts after looking at this yesterday, I'll do a full review during the week.

Comment thread src/wp-includes/class-wp-rewrite.php Outdated
$this->root = $this->index . '/';
}

$random_base = get_option( 'random_base' );

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.

Suggested change
$random_base = get_option( 'random_base' );
$random_base = get_option( 'wp_random_base' );

Rename throughout and add to schema.php

'wp_notes_notify' => 1,

Add to sanitize_option

case 'permalink_structure':
case 'category_base':
case 'tag_base':

Comment thread src/wp-includes/class-wp-rewrite.php
aaronjorbin and others added 2 commits October 14, 2026 15:55
Co-authored-by: Weston Ruter <westonruter@gmail.com>
@aaronjorbin

Copy link
Copy Markdown
Member Author

@peterwilsoncc pulling the discussion for the best way to store the wp_random_base out of the review and into the main thread so it isn't lost as code gets outdated.

Previously: #14018 (comment) and #14018 (comment)

Doing it in the upgrade routine means it's likely that an untranslated then since the language packs are downloaded after the core update. My thinking with changing it each time the default site language is changed is that people may install WordPress and then switch to the language they want. I know there is the language chooser as the first step of the Famous 5-Minute Install™, but I think it's likely not uncommon for someone to choose their preferred language there and then later change the site language to whatever they intend the site language to be. We could store "old_random_bases" and also use it then, but that feels like overkill.

Also, I think this explains why no one has attempted to make category base or tag base translatable :)

@peterwilsoncc peterwilsoncc left a comment •

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've added a few notes inline.

I also took the liberty of pushing c6f475f to add the new option to the schema.

Edit:

In fbc09a5 I fixed the assertion I broke. My understanding is that the options are best populated so they end up in alloptions which avoids additional DB queries to populate notoptions.

As WP_Rewrite defaults the value to an empty string if it's not set, no need to change the value in there.

Comment thread src/wp-includes/class-wp-rewrite.php
Comment thread src/wp-includes/class-wp-rewrite.php Outdated
Comment thread src/wp-includes/class-wp-post-type.php Outdated
$args['embeddable'] ??= $args['public'];

// If not set, default to the setting for 'publicly_queryable'.
$args['randomable'] ??= $args['publicly_queryable'];

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.

My thinking here is that the condition in the rewrite loop only adds the rewrite if the post type has an archive and a rewrite slug.

https://github.com/WordPress/wordpress-develop/pull/14018/changes#diff-9cf1519e03e09d5698082a4594d70539d9ce18aa16defbbae15a982583dec4efR1596-R1604

Comment thread src/wp-includes/class-wp.php Outdated
@peterwilsoncc

Copy link
Copy Markdown
Contributor

Doing it in the upgrade routine means it's likely that an untranslated then since the language packs are downloaded after the core update. My thinking with changing it each time the default site language is changed is that people may install WordPress and then switch to the language they want. I know there is the language chooser as the first step of the Famous 5-Minute Install™, but I think it's likely not uncommon for someone to choose their preferred language there and then later change the site language to whatever they intend the site language to be. We could store "old_random_bases" and also use it then, but that feels like overkill.

Also, I think this explains why no one has attempted to make category base or tag base translatable :)

Thanks @aaronjorbin, OOO issues makes a great deal of sense.

peterwilsoncc and others added 3 commits October 15, 2026 11:45
> Method WP_Rewrite::random_rewrite_rules() should return array<string, string> but returns array<string>.
Fixes these PHPStan errors:

Line | Error                                                           | Identifier
-----|-----------------------------------------------------------------|-------------------
1533 | Offset 0 of array assigned to variable $post_ids is never read. | array.unusedOffset
1533 | Offset 1 of array assigned to variable $post_ids is never read. | array.unusedOffset
1535 | Value assigned to $post_ids[] is never read.                    | assign.unused
}

if ( wp_is_random_content_redirect_enabled() && isset( $_POST['wp_random_base'] ) && is_string( $_POST['wp_random_base'] ) ) {
$random_base = $_POST['wp_random_base'];

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.

Suggested change
$random_base = $_POST['wp_random_base'];
$random_base = wp_unslash( $_POST['wp_random_base'] );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We aren't using wp_unslash for either the tag_base or the category_base. Is there a reason you think it's necessary here?

@westonruter westonruter Oct 15, 2026 •

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.

Mainly as a correctness issue. All such input vars get slashed by WordPress, so they should be unslashed when read out.

Also, WPCS will flag such input superglobals as a MissingUnslash error that are accessed without slashing: https://github.com/WordPress/WordPress-Coding-Standards/blob/510f4f678ac00b65c162aa0ad57d4768940a0409/WordPress/Sniffs/Security/ValidatedSanitizedInputSniff.php#L224

So all getters of these globals should be unslashed to prevent other devs from seeing it as a problem.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

WordPress.Security.ValidatedSanitizedInput isn't currently in the core ruleset and I think that is a separate conversation. I think consistency within core is more important and this builds off the rest of the file.

The chain for this getting unslashed is set_random_base() → update_option() → sanitize_option( 'wp_random_base' ) → sanitize_url() → esc_url() which has an allow list that doesn't include backslashes.

Comment thread src/wp-includes/query.php
Comment on lines +1578 to +1583
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Optional. Whether this is an existing post being updated. Default false.
* @param WP_Post|null $post_before Optional. Post object before the update, null for new posts. Default null.
*/
function wp_random_content_flush_rewrite_rules_for_post( int $post_id, WP_Post $post, bool $update = false, ?WP_Post $post_before = null ): void {

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.

PHPStan at higher rule levels is erroring here:

------ ------------------------------------------------------------------------------------------------------------------------------------------------------
  Line   src/wp-includes/query.php
 ------ ------------------------------------------------------------------------------------------------------------------------------------------------------
  1583   Function wp_random_content_flush_rewrite_rules_for_post() has an unused parameter $post_id.
         🪪  function.unusedParameter
  1583   Function wp_random_content_flush_rewrite_rules_for_post() has an unused parameter $update.
         🪪  function.unusedParameter
 ------ ------------------------------------------------------------------------------------------------------------------------------------------------------

However, given that these parameters are passed by the action, I think this is something we just have to ignore.

Comment thread src/wp-includes/version.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/query.php Outdated
Comment thread src/wp-includes/default-filters.php Outdated

// Random content redirects, see WP::handle_random().
add_filter( 'split_the_query', 'wp_random_content_split_the_query', 10, 2 );
add_filter( 'wp_headers', 'wp_random_content_headers' );

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.

Is this needed at all? Won't WP::handle_random() already be doing the redirection and setting the no-cache headers? The wp_random_content_headers() function seems redundant.

Suggested change
add_filter( 'wp_headers', 'wp_random_content_headers' );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch. Not putting in the batch I'm accepting so I can also remove that function.

Comment thread src/wp-includes/query.php Outdated
aaronjorbin and others added 5 commits October 15, 2026 16:38
Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
- Update documentation to clarify the conditions for the `randomable` property.
- Modify logic in `WP_Post_Type` and `WP_Rewrite` to ensure only post types with archives are considered randomable.
- Adjust tests to reflect changes in post type registration and random content selection.
- Add new tests for randomable post types without archives and ensure proper rewrite rules are applied.
wporg-sync pushed a commit that referenced this pull request Oct 16, 2026
Introduce a `random` query var and `/random/` rewrite rules[1], built on the existing rewrite, WP, and query APIs, that redirect to a randomly selected published post. Random requests[2] work for the whole site, taxonomy terms, authors and post type archives, with the base configurable on the Permalinks screen. A new `randomable` post type argument controls which content is eligible, and the selection runs through the main query so it remains filterable and costs a single database query.

The Random Base[3] is translatable and stored in the rewrite rules. This means that rewrite rules are regenerated when translations are updated or the Site Language is changed. Also, if a post or page is published that would result in the same slug as the Random Base, that content gets priority and nothing will be random[4].

This reintroduces the feature committed in r63767 and reverted in r63906.

1) Literally `/random/`, not just rules that are random. Though that could be fun too!
2) Not "random" as in unpredictable, but random as in someone wanted it to be random.
3) It's intentionally "random" but it could also intentionally be "aleatoria" or "karkovice".
4) The other random permalinks will still work, so not nothing. But is anything really random?

Developed in: #14018

Follow-up to r63767, r63906.

Props jorbin, peterwilsoncc, westonruter, mukesh27, annezazu, mlaetitia, mcsf, johnbillion, TobiasBg, solankisoftware, sajib1223, ellatrix, matt.
See #64498.


git-svn-id: https://develop.svn.wordpress.org/trunk@64402 602fd350-edb4-49c9-b593-d223f7449a82
@aaronjorbin

Copy link
Copy Markdown
Member Author

wporg-sync pushed a commit to WordPress/WordPress that referenced this pull request Oct 16, 2026
Introduce a `random` query var and `/random/` rewrite rules[1], built on the existing rewrite, WP, and query APIs, that redirect to a randomly selected published post. Random requests[2] work for the whole site, taxonomy terms, authors and post type archives, with the base configurable on the Permalinks screen. A new `randomable` post type argument controls which content is eligible, and the selection runs through the main query so it remains filterable and costs a single database query.

The Random Base[3] is translatable and stored in the rewrite rules. This means that rewrite rules are regenerated when translations are updated or the Site Language is changed. Also, if a post or page is published that would result in the same slug as the Random Base, that content gets priority and nothing will be random[4].

This reintroduces the feature committed in r63767 and reverted in r63906.

1) Literally `/random/`, not just rules that are random. Though that could be fun too!
2) Not "random" as in unpredictable, but random as in someone wanted it to be random.
3) It's intentionally "random" but it could also intentionally be "aleatoria" or "karkovice".
4) The other random permalinks will still work, so not nothing. But is anything really random?

Developed in: WordPress/wordpress-develop#14018

Follow-up to r63767, r63906.

Props jorbin, peterwilsoncc, westonruter, mukesh27, annezazu, mlaetitia, mcsf, johnbillion, TobiasBg, solankisoftware, sajib1223, ellatrix, matt.
See #64498.

Built from https://develop.svn.wordpress.org/trunk@64402


git-svn-id: http://core.svn.wordpress.org/trunk@63552 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@annezazu

Copy link
Copy Markdown

Thanks for seeing this through, all!

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.

5 participants