-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Rewrite Rules: Add opt-in support for random content redirects. #13022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1235,6 +1235,122 @@ function _find_post_by_old_date( $post_type ) { | |
| return $id; | ||
| } | ||
|
|
||
| /** | ||
| * Redirects requests for random content to a randomly selected published post. | ||
| * | ||
| * Handles requests using the `random` query parameter, e.g. `example.com/?random`. | ||
| * The `random_post_type` parameter restricts the pick to a post type (default `post`), | ||
| * and the `random_cat_id` parameter restricts it to a category (for post types using | ||
| * the `category` taxonomy). Only published, non-password-protected content from | ||
| * viewable post types is considered, and only GET/HEAD requests are handled. | ||
| * | ||
| * Disabled by default. To enable: | ||
| * | ||
| * add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
| * | ||
| * @since 7.2.0 | ||
| */ | ||
| function wp_random_content_redirect() { | ||
| // The `random` parameter is a flag, present with or without a value. | ||
| if ( ! isset( $_GET['random'] ) ) { | ||
| return; | ||
| } | ||
|
|
||
| /** | ||
| * Filters whether random content redirects are enabled. | ||
| * | ||
| * @since 7.2.0 | ||
| * | ||
| * @param bool $enabled Whether `?random` requests redirect to random content. Default false. | ||
| */ | ||
| if ( ! apply_filters( 'enable_random_content_redirect', false ) ) { | ||
| return; | ||
| } | ||
|
|
||
| $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( $_SERVER['REQUEST_METHOD'] ) : ''; | ||
|
|
||
| if ( ! in_array( $request_method, array( 'GET', 'HEAD' ), true ) ) { | ||
| return; | ||
| } | ||
|
|
||
| $post_type = 'post'; | ||
|
|
||
| if ( isset( $_GET['random_post_type'] ) ) { | ||
| $post_type = sanitize_key( wp_unslash( $_GET['random_post_type'] ) ); | ||
| } | ||
|
|
||
| $post_type_object = get_post_type_object( $post_type ); | ||
|
|
||
| if ( ! $post_type_object || ! is_post_type_viewable( $post_type_object ) ) { | ||
| return; | ||
| } | ||
|
|
||
| $args = array( | ||
| 'post_type' => $post_type, | ||
| 'post_status' => 'publish', | ||
| 'has_password' => false, | ||
| 'posts_per_page' => 1, | ||
| 'orderby' => 'ID', | ||
| 'order' => 'ASC', | ||
| 'ignore_sticky_posts' => true, | ||
| 'update_post_term_cache' => false, | ||
| 'update_post_meta_cache' => false, | ||
| ); | ||
|
|
||
| if ( isset( $_GET['random_cat_id'] ) ) { | ||
| $cat_id = (int) wp_unslash( $_GET['random_cat_id'] ); | ||
|
|
||
| if ( $cat_id < 1 ) { | ||
| return; | ||
| } | ||
|
|
||
| $args['cat'] = $cat_id; | ||
| } | ||
|
|
||
| // Pick via COUNT plus a random OFFSET rather than `ORDER BY RAND()`, | ||
| // which randomizes and sorts every candidate row on each request. | ||
| $query = new WP_Query( $args ); | ||
| $post_count = (int) $query->found_posts; | ||
|
|
||
| if ( $post_count > 1 ) { | ||
| $offset = wp_rand( 0, $post_count - 1 ); | ||
|
|
||
| if ( $offset > 0 ) { | ||
| $args['offset'] = $offset; | ||
| $args['no_found_rows'] = true; | ||
| $args['cache_results'] = false; | ||
|
|
||
| $query = new WP_Query( $args ); | ||
| } | ||
| } | ||
|
|
||
| if ( empty( $query->posts ) ) { | ||
| return; | ||
| } | ||
|
|
||
| $link = get_permalink( $query->posts[0] ); | ||
|
|
||
| /** | ||
| * Filters the random content redirect URL. | ||
| * | ||
| * Returning a falsey value cancels the redirect. | ||
| * | ||
| * @since 7.2.0 | ||
| * | ||
| * @param string $link The redirect URL. | ||
| */ | ||
| $link = apply_filters( 'random_content_redirect_url', $link ); | ||
|
|
||
| if ( ! $link ) { | ||
| return; | ||
| } | ||
|
|
||
| // Temporary redirect, so clients and intermediary caches do not cache the randomly picked target. | ||
| nocache_headers(); | ||
| wp_safe_redirect( $link, 302 ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also call
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 🙂
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks both for the comments. I ended up adding a
Thanks! Hadn't considered this properly |
||
| exit; | ||
| } | ||
|
|
||
| /** | ||
| * Set up global post data. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * @group rewrite | ||
| * @ticket 64498 | ||
| * @covers wp_random_content_redirect | ||
| */ | ||
| class Tests_Rewrite_RandomContentRedirect extends WP_UnitTestCase { | ||
| protected $random_content_redirect_url; | ||
|
|
||
| public function set_up() { | ||
| parent::set_up(); | ||
|
|
||
| add_filter( 'random_content_redirect_url', array( $this, 'filter_random_content_redirect_url' ) ); | ||
|
|
||
| $_GET['random'] = ''; | ||
| } | ||
|
|
||
| public function filter_random_content_redirect_url( $url ) { | ||
| $this->random_content_redirect_url = $url; | ||
| return false; | ||
| } | ||
|
|
||
| public function test_hook_is_registered() { | ||
| $this->assertSame( 10, has_action( 'template_redirect', 'wp_random_content_redirect' ) ); | ||
| } | ||
|
|
||
| public function test_disabled_by_default() { | ||
| self::factory()->post->create(); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
mlaetitia marked this conversation as resolved.
|
||
|
|
||
| public function test_no_random_parameter_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| unset( $_GET['random'] ); | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_redirects_to_a_published_post() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $post_ids = self::factory()->post->create_many( 3 ); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertContains( $this->random_content_redirect_url, array_map( 'get_permalink', $post_ids ) ); | ||
| } | ||
|
|
||
| public function test_post_request_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $_SERVER['REQUEST_METHOD'] = 'POST'; | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_unset_request_method_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| unset( $_SERVER['REQUEST_METHOD'] ); | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_head_request_redirects() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $_SERVER['REQUEST_METHOD'] = 'HEAD'; | ||
|
ellatrix marked this conversation as resolved.
|
||
|
|
||
| $post_id = self::factory()->post->create(); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( get_permalink( $post_id ), $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_excludes_password_protected_posts() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $public_id = self::factory()->post->create(); | ||
| self::factory()->post->create_many( 2, array( 'post_password' => 'secret' ) ); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( get_permalink( $public_id ), $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_excludes_unpublished_posts() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $public_id = self::factory()->post->create(); | ||
| self::factory()->post->create( array( 'post_status' => 'draft' ) ); | ||
| self::factory()->post->create( array( 'post_status' => 'private' ) ); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( get_permalink( $public_id ), $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_random_post_type_parameter() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| self::factory()->post->create(); | ||
| $page_id = self::factory()->post->create( array( 'post_type' => 'page' ) ); | ||
|
|
||
| $_GET['random_post_type'] = 'page'; | ||
|
ellatrix marked this conversation as resolved.
|
||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( get_permalink( $page_id ), $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_invalid_post_type_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| $_GET['random_post_type'] = 'nonexistent_type'; | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_non_viewable_post_type_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| register_post_type( 'wptests_hidden', array( 'public' => false ) ); | ||
|
|
||
| self::factory()->post->create(); | ||
| self::factory()->post->create( array( 'post_type' => 'wptests_hidden' ) ); | ||
|
|
||
| $_GET['random_post_type'] = 'wptests_hidden'; | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_random_cat_id_parameter() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $cat_id = self::factory()->category->create(); | ||
| $in_cat_id = self::factory()->post->create( array( 'post_category' => array( $cat_id ) ) ); | ||
| self::factory()->post->create_many( 3 ); | ||
|
|
||
| $_GET['random_cat_id'] = (string) $cat_id; | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( get_permalink( $in_cat_id ), $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_nonexistent_category_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| $_GET['random_cat_id'] = '99999'; | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| /** | ||
| * @dataProvider data_invalid_category_ids | ||
| * | ||
| * @param string $cat_id Invalid category ID value. | ||
| */ | ||
| public function test_invalid_category_id_does_not_redirect( $cat_id ) { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| $_GET['random_cat_id'] = $cat_id; | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function data_invalid_category_ids() { | ||
| return array( | ||
| 'zero' => array( '0' ), | ||
| 'non-numeric' => array( 'foo' ), | ||
| ); | ||
| } | ||
|
|
||
| public function test_negated_category_id_of_existing_category_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $cat_id = self::factory()->category->create(); | ||
| self::factory()->post->create( array( 'post_category' => array( $cat_id ) ) ); | ||
|
|
||
| $_GET['random_cat_id'] = (string) ( -$cat_id ); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_redirect_url_is_filterable() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| self::factory()->post->create(); | ||
|
|
||
| add_filter( | ||
| 'random_content_redirect_url', | ||
| static function () { | ||
| return home_url( '/custom-target/' ); | ||
| }, | ||
| 9 | ||
| ); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( home_url( '/custom-target/' ), $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_no_posts_does_not_redirect() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertNull( $this->random_content_redirect_url ); | ||
| } | ||
|
|
||
| public function test_single_post_redirects_to_it() { | ||
| add_filter( 'enable_random_content_redirect', '__return_true' ); | ||
|
|
||
| $post_id = self::factory()->post->create(); | ||
|
|
||
| wp_random_content_redirect(); | ||
| $this->assertSame( get_permalink( $post_id ), $this->random_content_redirect_url ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
absintalways returns positive? Should it be(int)instead maybe?There was a problem hiding this comment.
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