Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/wp-admin/includes/schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,9 @@ function populate_options( array $options = array() ) {

// 6.9.0
'wp_notes_notify' => 1,

// 7.2.0
'wp_random_base' => '',
);

// 3.3.0
Expand Down
2 changes: 1 addition & 1 deletion src/wp-admin/includes/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2892,7 +2892,7 @@ function __get_option( $setting ) { // phpcs:ignore WordPress.NamingConventions.
return __get_option( 'siteurl' );
}

if ( in_array( $setting, array( 'siteurl', 'home', 'category_base', 'tag_base' ), true ) ) {
if ( in_array( $setting, array( 'siteurl', 'home', 'category_base', 'tag_base', 'wp_random_base' ), true ) ) {
$option = untrailingslashit( $option );
}

Expand Down
1 change: 1 addition & 0 deletions src/wp-admin/network/site-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
'permalink_structure',
'category_base',
'tag_base',
'wp_random_base',
'upload_path',
'upload_url_path',
);
Expand Down
39 changes: 39 additions & 0 deletions src/wp-admin/options-permalink.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
get_current_screen()->set_help_sidebar( $help_sidebar_content );
unset( $help_sidebar_content );

/** @global WP_Rewrite $wp_rewrite WordPress rewrite component. */
global $wp_rewrite;

$home_path = get_home_path();
$iis7_permalinks = iis7_supports_permalinks();
$permalink_structure = get_option( 'permalink_structure' );
Expand All @@ -94,6 +97,8 @@

$category_base = get_option( 'category_base' );
$tag_base = get_option( 'tag_base' );
$random_base = get_option( 'wp_random_base' );
$random_base = is_string( $random_base ) ? $random_base : '';

$structure_updated = false;
$htaccess_update_required = false;
Expand Down Expand Up @@ -144,6 +149,16 @@

$wp_rewrite->set_tag_base( $tag_base );
}

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.


if ( ! empty( $random_base ) ) {
$random_base = $blog_prefix . preg_replace( '#/+#', '/', '/' . str_replace( '#', '', $random_base ) );
}

$wp_rewrite->set_random_base( $random_base );
}
}

if ( $iis7_permalinks ) {
Expand Down Expand Up @@ -236,6 +251,7 @@
$permalink_structure = preg_replace( '|^/?blog|', '', $permalink_structure );
$category_base = preg_replace( '|^/?blog|', '', $category_base );
$tag_base = preg_replace( '|^/?blog|', '', $tag_base );
$random_base = preg_replace( '|^/?blog|', '', $random_base ) ?? '';
}

$url_base = home_url( $blog_prefix . $index_php_prefix );
Expand Down Expand Up @@ -457,6 +473,29 @@ class="button button-secondary"
<?php endif; ?>
</td>
</tr>
<?php if ( wp_is_random_content_redirect_enabled() ) : ?>
<tr>
<th>
<label for="wp_random_base">
<?php _e( 'Random base' ); ?>
</label>
</th>
<td>
<?php if ( '' === $blog_prefix ) : ?>
<input name="wp_random_base" id="wp_random_base" type="text"
value="<?php echo esc_attr( $random_base ); ?>" class="regular-text code"
/>
<?php else : ?>
<span class="code permalink-structure-has-blog-prefix">
<code class="no-break"><?php echo $blog_prefix; ?></code>
<input name="wp_random_base" id="wp_random_base" type="text"
value="<?php echo esc_attr( $random_base ); ?>" class="regular-text code"
/>
</span>
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
<?php do_settings_fields( 'permalink', 'optional' ); ?>
</table>

Expand Down
17 changes: 17 additions & 0 deletions src/wp-includes/class-wp-post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ final class WP_Post_Type {
*/
public $embeddable = null;

/**
* Whether posts of this post type can be selected by random content redirects.
*
* Random content redirects send requests such as `example.com/random/` to a
* randomly selected post.
*
* Default is true when $publicly_queryable is true and the post type has an archive.
*
* @since 7.2.0
* @var bool $randomable
*/
public $randomable = null;

/**
* Whether to generate and allow a UI for managing this post type in the admin.
*
Expand Down Expand Up @@ -535,6 +548,7 @@ public function set_props( $args ) {
'exclude_from_search' => null,
'publicly_queryable' => null,
'embeddable' => null,
'randomable' => null,
'show_ui' => null,
'show_in_menu' => null,
'show_in_nav_menus' => null,
Expand Down Expand Up @@ -578,6 +592,9 @@ public function set_props( $args ) {
// If not set, default to the setting for 'public'.
$args['embeddable'] ??= $args['public'];

// If not set, default to publicly queryable post types with an archive.
$args['randomable'] ??= $args['publicly_queryable'] && (bool) $args['has_archive'];

// If not set, default rest_namespace to wp/v2 if show_in_rest is true.
if ( false === $args['rest_namespace'] && ! empty( $args['show_in_rest'] ) ) {
$args['rest_namespace'] = 'wp/v2';
Expand Down
28 changes: 28 additions & 0 deletions src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ class WP_Query {
*/
public bool $is_sitemap = false;

/**
* Signifies whether the current query is for random content.
*
* True for a blog home, archive or search request that includes the `random`
* query variable, for example `example.com/random/` or `example.com/?s=foo&random`.
*
* @since 7.2.0
*/
public bool $is_random = false;

/**
* Signifies whether the current query is for the page_for_posts page.
*
Expand Down Expand Up @@ -527,6 +537,7 @@ private function init_query_flags() {
$this->is_robots = false;
$this->is_favicon = false;
$this->is_sitemap = false;
$this->is_random = false;
$this->is_posts_page = false;
$this->is_post_type_archive = false;
}
Expand Down Expand Up @@ -1147,6 +1158,10 @@ public function parse_query( $query = '' ) {

$this->is_embed = $this->is_embed && ( $this->is_singular || $this->is_404 );

$this->is_random = isset( $query_vars['random'] )
&& ! $this->is_feed
&& ( $this->is_home || $this->is_archive || $this->is_search );

$this->query_vars_hash = md5( serialize( $this->query_vars ) );
$this->query_vars_changed = false;

Expand Down Expand Up @@ -4653,6 +4668,19 @@ public function is_sitemap(): bool {
return $this->is_sitemap;
}

/**
* Determines whether the query is for random content.
*
* @since 7.2.0
*
* @see wp_is_random_content_query()
*
* @return bool Whether the query is for random content.
*/
public function is_random(): bool {
return $this->is_random;
}

/**
* Determines whether the query is for a search.
*
Expand Down
Loading
Loading