Make WordPress Core

Changeset 65441


Ignore:
Timestamp:
11/12/2026 10:23:49 PM (less than one hour ago)
Author:
jorbin
Message:

Rewrite Rules: Give top rewrite rules priority over random content rules.

A post type whose archive is at the random base, by name, rewrite slug or has_archive, kept its rewrite rule but had the rule's query replaced by the random content query. As a result /random/ either redirected to random content or returned a 404 instead of displaying the post type archive. The same applied to any rule a plugin added to the top using the same regex as a random content rule.

Random content rules using the same regex as a rule added to the top, including post type and taxonomy rules, are now omitted when the rewrite rules are generated.

Developed in: ​https://github.com/WordPress/wordpress-develop/pull/15959 and ​https://github.com/WordPress/wordpress-develop/pull/15838

Follow-up to r64402.

Props peterwilsoncc, jorbin, ssebuwufumoses, aryanjasala.
Fixes #66630. See #64498.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-rewrite.php

    r64690 r65441  
    14241424                $page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
    14251425
    1426                 // Random content rewrite rules.
    1427                 $random_rewrite = $this->random_rewrite_rules();
    1428 
    1429                 /**
    1430                  * Filters rewrite rules used for random content redirects.
    1431                  *
    1432                  * Likely random content rules would include `/random/`, as well as
    1433                  * term, author and post type archives prefixed with the random base,
    1434                  * for example `/random/category/news/`.
    1435                  *
    1436                  * @since 7.2.0
    1437                  *
    1438                  * @param array<string, string> $random_rewrite Array of rewrite rules for random content redirects, keyed by their regex pattern.
    1439                  */
    1440                 $random_rewrite = apply_filters( 'random_rewrite_rules', $random_rewrite );
    1441 
    14421426                // Extra permastructs.
    14431427                foreach ( $this->extra_permastructs as $permastructname => $struct ) {
    … …  
    14861470                }
    14871471
    1488                 // Put them together.
     1472                // Random content rewrite rules.
     1473                $random_rewrite = $this->random_rewrite_rules();
     1474
     1475                /*
     1476                 * Rules added to the top, including those for post types and taxonomies, take
     1477                 * precedence over random content rules using the same regex, for example a post
     1478                 * type archive at `/random/`.
     1479                 *
     1480                 * Rules added to the top are merged ahead of the random content rules, so without
     1481                 * this, the random content rule's query would replace the query of a rule using the
     1482                 * same regex. Rules added to the top using a different regex for the same path,
     1483                 * such as `^random/?$`, already take precedence as they are matched first.
     1484                 */
     1485                $random_rewrite = array_diff_key( $random_rewrite, $this->extra_rules_top );
     1486
     1487                /**
     1488                 * Filters rewrite rules used for random content redirects.
     1489                 *
     1490                 * Likely random content rules would include `/random/`, as well as
     1491                 * term, author and post type archives prefixed with the random base,
     1492                 * for example `/random/category/news/`.
     1493                 *
     1494                 * @since 7.2.0
     1495                 *
     1496                 * @param array<string, string> $random_rewrite Array of rewrite rules for random content redirects, keyed by their regex pattern.
     1497                 */
     1498                $random_rewrite = apply_filters( 'random_rewrite_rules', $random_rewrite );
     1499
     1500                /*
     1501                 * Put them together, with the random content rules ahead of the generic rules
     1502                 * such as the page and post rules which would otherwise match them first.
     1503                 */
    14891504                if ( $this->use_verbose_page_rules ) {
    14901505                        $this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $sitemap_rewrite, $deprecated_files, $registration_pages, $random_rewrite, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $page_rewrite, $post_rewrite, $this->extra_rules );
    … …  
    15321547         * the URL, see {@see WP_Rewrite::post_permalinks_share_random_base()}. The rules are flushed
    15331548         * when such a page or post changes, see {@see wp_random_content_flush_rewrite_rules_for_post()}.
     1549         *
     1550         * Rules added to the top of the rewrite rules, including those for post types and taxonomies,
     1551         * take precedence over the random content rules. When the rewrite rules are generated, random
     1552         * content rules using the same regex as a rule added to the top are omitted, see
     1553         * {@see WP_Rewrite::rewrite_rules()}. For example, a post type archive at `/random/` replaces
     1554         * the `/random/` rule.
     1555         *
     1556         * If a post type or taxonomy uses the random base as its rewrite slug, its rules may match
     1557         * random archive URLs before the random content rules do. For example, the attachment rules
     1558         * of a post type with the rewrite slug `random` match `/random/category/news/`, and the rules
     1559         * of a hierarchical post type or taxonomy match every URL beginning with `/random/`. Such
     1560         * random archive URLs display the post type or taxonomy content instead. To use random
     1561         * archives on these sites, change the random base on the Permalinks screen.
    15341562         *
    15351563         * The `random` query variable can be added to any other archive or search URL, for example
  • trunk/tests/phpunit/tests/query/randomContentRedirect.php

    r65440 r65441  
    105105
    106106        /**
     107         * Rewrite rules added to the top of the rules before each test.
     108         *
     109         * WP_Rewrite::init() does not reset these, so they are restored after each test.
     110         *
     111         * @var string[]
     112         */
     113        protected $original_extra_rules_top = array();
     114
     115        /**
     116         * Extra permalink structures registered before each test.
     117         *
     118         * Unregistering post types between tests does not remove their permalink structures,
     119         * so they are restored after each test.
     120         *
     121         * @var array<array<mixed>>
     122         */
     123        protected $original_extra_permastructs = array();
     124
     125        /**
    107126         * Creates shared user, category and post fixtures.
    108127         *
    … …  
    146165        /**
    147166         * Sets up pretty permalinks and redirect capturing before each test.
     167         *
     168         * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
    148169         */
    149170        public function set_up(): void {
     171                global $wp_rewrite;
     172
    150173                parent::set_up();
     174
     175                $this->original_extra_rules_top    = $wp_rewrite->extra_rules_top;
     176                $this->original_extra_permastructs = $wp_rewrite->extra_permastructs;
    151177
    152178                $this->set_permalink_structure( '/%postname%/' );
    … …  
    166192        /**
    167193         * Unregisters the test post types and taxonomy after each test.
     194         *
     195         * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
    168196         */
    169197        public function tear_down(): void {
     198                global $wp_rewrite;
     199
     200                $wp_rewrite->extra_rules_top    = $this->original_extra_rules_top;
     201                $wp_rewrite->extra_permastructs = $this->original_extra_permastructs;
     202
    170203                unregister_post_type( 'wptests_book' );
    171204                unregister_post_type( 'wptests_hidden' );
    … …  
    26982731                $this->assertRedirectedToOneOf( array( self::$arts_post, self::$sport_post ), 'Random content redirects should resume when the post named random is unpublished.' );
    26992732        }
     2733
     2734        /**
     2735         * Ensure a post type archive at the random base is not redirected to random content.
     2736         *
     2737         * @dataProvider data_post_types_at_the_random_base
     2738         *
     2739         * @param string               $post_type Post type name.
     2740         * @param array<string, mixed> $args      Post type registration arguments.
     2741         *
     2742         * @phpstan-param lowercase-string&non-falsy-string $post_type
     2743         * @phpstan-param array{public: bool, has_archive: bool|string, rewrite?: array{slug: string}} $args
     2744         */
     2745        public function test_post_type_archive_at_the_random_base_is_not_redirected( string $post_type, array $args ): void {
     2746                register_post_type( $post_type, $args );
     2747                flush_rewrite_rules();
     2748
     2749                $this->create_post( array( 'post_type' => $post_type ) );
     2750
     2751                $this->go_to( home_url( '/random/' ) );
     2752
     2753                $this->assertNotRedirected( 'The post type archive at the random base should not redirect to random content.' );
     2754                $this->assertQueryTrue( 'is_archive', 'is_post_type_archive' );
     2755                $this->assertSame( $post_type, get_query_var( 'post_type' ), 'The post type archive at the random base should be queried.' );
     2756        }
     2757
     2758        /**
     2759         * Ensure a single post of a post type at the random base is not redirected to random content.
     2760         *
     2761         * Registering a post type named `random` also registers `random` as its query variable,
     2762         * which is the query variable used by random content requests.
     2763         *
     2764         * @dataProvider data_post_types_at_the_random_base
     2765         *
     2766         * @param string               $post_type Post type name.
     2767         * @param array<string, mixed> $args      Post type registration arguments.
     2768         *
     2769         * @phpstan-param lowercase-string&non-falsy-string $post_type
     2770         * @phpstan-param array{public: bool, has_archive: bool|string, rewrite?: array{slug: string}} $args
     2771         */
     2772        public function test_single_post_of_a_post_type_at_the_random_base_is_not_redirected( string $post_type, array $args ): void {
     2773                register_post_type( $post_type, $args );
     2774                flush_rewrite_rules();
     2775
     2776                $post_id = self::factory()->post->create( array( 'post_type' => $post_type ) );
     2777                self::factory()->post->create();
     2778
     2779                $permalink = get_permalink( $post_id );
     2780                $this->assertIsString( $permalink, 'The post permalink could not be retrieved.' );
     2781                $this->assertStringStartsWith( home_url( '/' . ( $args['rewrite']['slug'] ?? $post_type ) . '/' ), $permalink, 'The post permalink should use the post type rewrite slug.' );
     2782
     2783                $redirect = new MockAction();
     2784                add_filter( 'wp_redirect', array( $redirect, 'filter' ), 5 );
     2785
     2786                $this->go_to( $permalink );
     2787
     2788                $this->assertSame( 0, $redirect->get_call_count(), 'The single post should not redirect to random content.' );
     2789                $this->assertQueryTrue( 'is_single', 'is_singular' );
     2790                $this->assertSame( $post_id, get_queried_object_id(), 'The single post should be queried.' );
     2791        }
     2792
     2793        /**
     2794         * Data provider for post types registered with an archive or single posts at the random base.
     2795         *
     2796         * @return array<string, array{0: string, 1: array<string, mixed>}>
     2797         */
     2798        public static function data_post_types_at_the_random_base(): array {
     2799                return array(
     2800                        'post type named random'          => array(
     2801                                'random',
     2802                                array(
     2803                                        'public'      => true,
     2804                                        'has_archive' => true,
     2805                                ),
     2806                        ),
     2807                        'archive slug random'             => array(
     2808                                'wptests_random',
     2809                                array(
     2810                                        'public'      => true,
     2811                                        'has_archive' => 'random',
     2812                                ),
     2813                        ),
     2814                        'archive and rewrite slug random' => array(
     2815                                'wptests_random',
     2816                                array(
     2817                                        'public'      => true,
     2818                                        'has_archive' => 'random',
     2819                                        'rewrite'     => array( 'slug' => 'random' ),
     2820                                ),
     2821                        ),
     2822                        'rewrite slug random'             => array(
     2823                                'wptests_random',
     2824                                array(
     2825                                        'public'      => true,
     2826                                        'has_archive' => true,
     2827                                        'rewrite'     => array( 'slug' => 'random' ),
     2828                                ),
     2829                        ),
     2830                );
     2831        }
     2832
     2833        /**
     2834         * Ensure random archives redirect when a post type archive is at the random base.
     2835         */
     2836        public function test_random_archives_redirect_when_a_post_type_archive_is_at_the_random_base(): void {
     2837                register_post_type(
     2838                        'wptests_random',
     2839                        array(
     2840                                'public'      => true,
     2841                                'has_archive' => 'random',
     2842                        )
     2843                );
     2844                flush_rewrite_rules();
     2845
     2846                $this->go_to( home_url( '/random/category/arts/' ) );
     2847
     2848                $this->assertSame( get_permalink( self::$arts_post ), $this->redirect_location, 'Random archives should redirect when a post type archive is at the random base.' );
     2849        }
     2850
     2851        /**
     2852         * Ensure the random base is not redirected to random content when an existing rule takes it.
     2853         *
     2854         * The dataProvider supplies a top-priority rewrite rule, each one a different regex spelling that
     2855         * matches the path `/random` and points to a specific `page`. This ensures that rewrite rule
     2856         * takes priority over the random ones.
     2857         *
     2858         * @dataProvider data_random_base_is_not_redirected_when_an_existing_rule_takes_it
     2859         *
     2860         * @param string $regex Rewrite rule regex.
     2861         */
     2862        public function test_random_base_is_not_redirected_when_an_existing_rule_takes_it( string $regex ): void {
     2863                $page = $this->create_post(
     2864                        array(
     2865                                'post_type' => 'page',
     2866                                'post_name' => 'wptests-taken',
     2867                        )
     2868                );
     2869
     2870                add_rewrite_rule( $regex, 'index.php?pagename=wptests-taken', 'top' );
     2871                flush_rewrite_rules();
     2872
     2873                $this->go_to( home_url( '/random/' ) );
     2874
     2875                $this->assertNotRedirected( "The random base should not redirect to random content when the rule {$regex} takes it." );
     2876                $this->assertSame( $page, get_queried_object_id(), "The page served by the rule {$regex} should be queried." );
     2877        }
     2878
     2879        /**
     2880         * Data provider.
     2881         *
     2882         * @return array<string, string[]>
     2883         */
     2884        public static function data_random_base_is_not_redirected_when_an_existing_rule_takes_it(): array {
     2885                return array(
     2886                        'exact match'          => array( 'random/?$' ),
     2887                        'anchored exact match' => array( '^random/?$' ),
     2888                        'alternation'          => array( '(random|pens|pencils)/?$' ),
     2889                        'partial group'        => array( '(rand)om/?$' ),
     2890                        'character class'      => array( '[r]andom/?$' ),
     2891                        'optional suffix'      => array( 'random(?:-ful)?/?$' ),
     2892                );
     2893        }
     2894
     2895        /**
     2896         * Ensure random archives redirect when an existing rule takes the random base.
     2897         */
     2898        public function test_random_archives_redirect_when_an_existing_rule_takes_the_random_base(): void {
     2899                add_rewrite_rule( '^random/?$', 'index.php?pagename=wptests-taken', 'top' );
     2900                flush_rewrite_rules();
     2901
     2902                $this->go_to( home_url( '/random/category/arts/' ) );
     2903
     2904                $this->assertSame( get_permalink( self::$arts_post ), $this->redirect_location, 'Random archives should redirect when an existing rule takes the random base.' );
     2905        }
     2906
     2907        /**
     2908         * Ensure the random base redirects to random content when existing rules do not take it.
     2909         *
     2910         * @dataProvider data_random_base_redirects_when_existing_rules_do_not_take_it
     2911         *
     2912         * @param string $regex    Rewrite rule regex.
     2913         * @param string $position Position of the rewrite rule, either 'top' or 'bottom'.
     2914         */
     2915        public function test_random_base_redirects_when_existing_rules_do_not_take_it( string $regex, string $position ): void {
     2916                add_rewrite_rule( $regex, 'index.php?pagename=wptests-not-taken', $position );
     2917                flush_rewrite_rules();
     2918
     2919                $this->go_to( home_url( '/random/' ) );
     2920
     2921                $this->assertRedirectedToOneOf( array( self::$arts_post, self::$sport_post ), "The random base should redirect to random content when the rule {$regex} does not take it." );
     2922        }
     2923
     2924        /**
     2925         * Data provider.
     2926         *
     2927         * @return array<string, string[]>
     2928         */
     2929        public static function data_random_base_redirects_when_existing_rules_do_not_take_it(): array {
     2930                return array(
     2931                        'suffixed slug'            => array( 'randomful/?$', 'top' ),
     2932                        'hyphenated suffixed slug' => array( '^random-ful/?$', 'top' ),
     2933                        'similar alternation'      => array( '(randomful|random-ful)/?$', 'top' ),
     2934                        'sub path'                 => array( '^random/wptests/?$', 'top' ),
     2935                        'requires a sub path'      => array( '^random/(.*)?', 'top' ),
     2936                        'catch-all'                => array( '(.+?)/?$', 'bottom' ),
     2937                        'single segment catch-all' => array( '([^/]+)/?$', 'bottom' ),
     2938                        'bottom anchored match'    => array( '^random/?$', 'bottom' ),
     2939                        'bottom alternation'       => array( '(random|pens|pencils)/?$', 'bottom' ),
     2940                        'bottom unterminated'      => array( 'random', 'bottom' ),
     2941                );
     2942        }
     2943
     2944        /**
     2945         * Ensure a bottom rule using the random root rule's regex takes the random base.
     2946         *
     2947         * Rules added to the bottom are preceded by the generic page and post rules, so are
     2948         * never matched for the random base. A bottom rule using the same regex as the random
     2949         * root rule replaces the root rule's query while keeping its position ahead of the
     2950         * page and post rules, so takes the random base.
     2951         */
     2952        public function test_bottom_rule_using_the_random_root_rule_regex_takes_the_random_base(): void {
     2953                $page = $this->create_post(
     2954                        array(
     2955                                'post_type' => 'page',
     2956                                'post_name' => 'wptests-taken',
     2957                        )
     2958                );
     2959
     2960                add_rewrite_rule( 'random/?$', 'index.php?pagename=wptests-taken', 'bottom' );
     2961                flush_rewrite_rules();
     2962
     2963                $this->go_to( home_url( '/random/' ) );
     2964
     2965                $this->assertNotRedirected( 'A bottom rule using the root rule regex should not redirect to random content.' );
     2966                $this->assertQueryTrue( 'is_page', 'is_singular' );
     2967                $this->assertSame( $page, get_queried_object_id(), 'The page served by the bottom rule using the root rule regex should be queried.' );
     2968        }
     2969
     2970        /**
     2971         * Ensure a category at the random base with the category base removed is not redirected to random content.
     2972         *
     2973         * This replicates plugins that remove the category base from category archive URLs.
     2974         */
     2975        public function test_category_at_the_random_base_without_a_category_base_is_not_redirected(): void {
     2976                $category = self::factory()->category->create( array( 'slug' => 'random' ) );
     2977
     2978                add_rewrite_rule( '(random)/?$', 'index.php?category_name=$matches[1]', 'top' );
     2979                flush_rewrite_rules();
     2980
     2981                $this->go_to( home_url( '/random/' ) );
     2982
     2983                $this->assertNotRedirected( 'A category at the random base should not redirect to random content.' );
     2984                $this->assertQueryTrue( 'is_archive', 'is_category' );
     2985                $this->assertSame( $category, get_queried_object_id(), 'The category at the random base should be queried.' );
     2986        }
    27002987}
  • trunk/tests/phpunit/tests/rewrite/randomRewriteRules.php

    r65440 r65441  
    2020
    2121        /**
     22         * Rewrite rules added to the top of the rules before each test.
     23         *
     24         * WP_Rewrite::init() does not reset these, so they are restored after each test.
     25         *
     26         * @var string[]
     27         */
     28        private $original_extra_rules_top = array();
     29
     30        /**
     31         * Extra permalink structures registered before each test.
     32         *
     33         * Unregistering post types between tests does not remove their permalink structures,
     34         * so they are restored after each test.
     35         *
     36         * @var array<array<mixed>>
     37         */
     38        private $original_extra_permastructs = array();
     39
     40        /**
    2241         * Requests a URL, stopping where a random content redirect ends the request.
    2342         *
    … …  
    3453        }
    3554
     55        /**
     56         * Sets up pretty permalinks before each test.
     57         *
     58         * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
     59         */
    3660        public function set_up(): void {
     61                global $wp_rewrite;
     62
    3763                parent::set_up();
     64
     65                $this->original_extra_rules_top    = $wp_rewrite->extra_rules_top;
     66                $this->original_extra_permastructs = $wp_rewrite->extra_permastructs;
    3867
    3968                // End the request before random content redirects exit, only the rewrite rules are under test.
    … …  
    5079        }
    5180
     81        /**
     82         * Restores the rewrite rules and random base after each test.
     83         *
     84         * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
     85         */
    5286        public function tear_down(): void {
     87                global $wp_rewrite;
     88
     89                $wp_rewrite->extra_rules_top    = $this->original_extra_rules_top;
     90                $wp_rewrite->extra_permastructs = $this->original_extra_permastructs;
     91
    5392                /*
    5493                 * The parent class re-initializes the rewrite base from this option before the
    … …  
    667706         * such as the greedy page and post rules.
    668707         *
    669          * @dataProvider data_permalink_structures
     708         * @dataProvider data_random_rewrite_rules_precede_other_matching_rules
    670709         *
    671710         * @param string $structure Permalink structure.
    … …  
    705744         * @return array<string, string[]>
    706745         */
    707         public static function data_permalink_structures(): array {
     746        public static function data_random_rewrite_rules_precede_other_matching_rules(): array {
    708747                return array(
    709748                        'post name'          => array( '/%postname%/' ),
    … …  
    720759         * @param string                $path           Request path.
    721760         * @param array<string, string> $expected_query Expected query variables.
    722          */
    723         public function test_random_urls_resolve_to_random_query_vars( $path, $expected_query ): void {
     761         * @param string                $structure      Optional. Permalink structure. Default empty, the
     762         *                                              structure set up for each test.
     763         */
     764        public function test_random_urls_resolve_to_random_query_vars( $path, $expected_query, string $structure = '' ): void {
    724765                global $wp;
     766
     767                if ( '' !== $structure ) {
     768                        $this->set_permalink_structure( $structure );
     769                        create_initial_taxonomies();
     770                }
    725771
    726772                register_taxonomy( 'wptests_genre', 'post', array( 'rewrite' => array( 'slug' => 'genre' ) ) );
    … …  
    745791         * Data provider.
    746792         *
    747          * @return array<string, array{0: string, 1: array<string, string>}>
     793         * @return array<string, array{0: string, 1: array<string, string>, 2?: string}>
    748794         */
    749795        public static function data_random_urls(): array {
    750796                return array(
    751                         'random'                    => array( '/random/', array( 'random' => '1' ) ),
    752                         'random without slash'      => array( '/random', array( 'random' => '1' ) ),
    753                         'random category'           => array(
     797                        'random'                     => array( '/random/', array( 'random' => '1' ) ),
     798                        'random without slash'       => array( '/random', array( 'random' => '1' ) ),
     799                        'random, verbose page rules' => array( '/random/', array( 'random' => '1' ), '/%category%/%postname%/' ),
     800                        'random, date and name'      => array( '/random/', array( 'random' => '1' ), '/%year%/%monthnum%/%postname%/' ),
     801                        'random category'            => array(
    754802                                '/random/category/news/',
    755803                                array(
    … …  
    758806                                ),
    759807                        ),
    760                         'random child category'     => array(
     808                        'random child category'      => array(
    761809                                '/random/category/news/local/',
    762810                                array(
    … …  
    765813                                ),
    766814                        ),
    767                         'random tag'                => array(
     815                        'random tag'                 => array(
    768816                                '/random/tag/featured/',
    769817                                array(
    … …  
    772820                                ),
    773821                        ),
    774                         'random author'             => array(
     822                        'random author'              => array(
    775823                                '/random/author/admin/',
    776824                                array(
    … …  
    779827                                ),
    780828                        ),
    781                         'random custom taxonomy'    => array(
     829                        'random custom taxonomy'     => array(
    782830                                '/random/genre/sci-fi/',
    783831                                array(
    … …  
    786834                                ),
    787835                        ),
    788                         'random post type archive'  => array(
     836                        'random post type archive'   => array(
    789837                                '/random/books/',
    790838                                array(
    … …  
    793841                                ),
    794842                        ),
    795                         'query string random'       => array( '/?random', array( 'random' => '' ) ),
    796                         'query string random value' => array( '/?random=1', array( 'random' => '1' ) ),
     843                        'query string random'        => array( '/?random', array( 'random' => '' ) ),
     844                        'query string random value'  => array( '/?random=1', array( 'random' => '1' ) ),
    797845                );
    798846        }
    … …  
    10551103                );
    10561104        }
     1105
     1106        /**
     1107         * Ensure random rules using the same regex as a rule added to the top are omitted.
     1108         *
     1109         * Rules added to the top are merged ahead of the random rules, so a random rule using
     1110         * the same regex would otherwise replace the top rule's query.
     1111         *
     1112         * @dataProvider data_random_rules_using_the_regex_of_a_top_rule_are_omitted
     1113         *
     1114         * @param string $regex       Rewrite rule regex.
     1115         * @param string $random_rule A random rule using a different regex, expected to be kept.
     1116         */
     1117        public function test_random_rules_using_the_regex_of_a_top_rule_are_omitted( string $regex, string $random_rule ): void {
     1118                add_rewrite_rule( $regex, 'index.php?pagename=wptests-taken', 'top' );
     1119
     1120                $this->get_random_rewrite_rules();
     1121                $rules = $this->get_stored_rewrite_rules();
     1122
     1123                $this->assertSame( 'index.php?pagename=wptests-taken', $rules[ $regex ] ?? null, "The top rule {$regex} should take precedence over the random rule using the same regex." );
     1124                $random_query = $rules[ $random_rule ] ?? '';
     1125                $this->assertIsString( $random_query, "The random rule {$random_rule} should be kept when the top rule {$regex} uses a different regex." );
     1126                $this->assertStringContainsString( 'random=1', $random_query, "The random rule {$random_rule} should be kept when the top rule {$regex} uses a different regex." );
     1127        }
     1128
     1129        /**
     1130         * Data provider.
     1131         *
     1132         * @return array<string, string[]>
     1133         */
     1134        public static function data_random_rules_using_the_regex_of_a_top_rule_are_omitted(): array {
     1135                return array(
     1136                        'root rule'             => array( 'random/?$', 'random/category/(.+?)/?$' ),
     1137                        'category archive rule' => array( 'random/category/(.+?)/?$', 'random/?$' ),
     1138                );
     1139        }
     1140
     1141        /**
     1142         * Ensure random rules are kept when a rule added to the top uses a different regex for the random base.
     1143         *
     1144         * Such rules are matched before the random rules, so take precedence without the random rules being omitted.
     1145         *
     1146         * @global WP $wp Current WordPress environment instance.
     1147         */
     1148        public function test_random_root_rule_is_kept_when_a_top_rule_uses_a_different_regex_for_the_random_base(): void {
     1149                global $wp;
     1150
     1151                $page = self::factory()->post->create(
     1152                        array(
     1153                                'post_type' => 'page',
     1154                                'post_name' => 'wptests-taken',
     1155                        )
     1156                );
     1157
     1158                add_rewrite_rule( '^random/?$', 'index.php?pagename=wptests-taken', 'top' );
     1159
     1160                $this->get_random_rewrite_rules();
     1161
     1162                $this->assertSame( 'index.php?random=1', $this->get_stored_rewrite_rules()['random/?$'] ?? null, 'The root rule should be kept when a top rule uses a different regex.' );
     1163
     1164                $this->go_to( home_url( '/random/' ) );
     1165
     1166                $this->assertSame( '^random/?$', $wp->matched_rule, 'The top rule should be matched before the root rule.' );
     1167                $this->assertSame( $page, get_queried_object_id(), 'The page served by the top rule should be queried.' );
     1168        }
     1169
     1170        /**
     1171         * Ensure a post type archive at the random base takes precedence over the root rule.
     1172         *
     1173         * @dataProvider data_random_root_rule_is_omitted_for_a_post_type_archive_at_the_random_base
     1174         *
     1175         * @param string               $post_type Post type name.
     1176         * @param array<string, mixed> $args      Post type registration arguments.
     1177         *
     1178         * @phpstan-param lowercase-string&non-falsy-string $post_type
     1179         * @phpstan-param array{public: bool, has_archive: bool|string, rewrite?: array{slug: string}} $args
     1180         */
     1181        public function test_random_root_rule_is_omitted_for_a_post_type_archive_at_the_random_base( string $post_type, array $args ): void {
     1182                register_post_type( $post_type, $args );
     1183
     1184                $this->get_random_rewrite_rules();
     1185
     1186                $this->assertSame( "index.php?post_type={$post_type}", $this->get_stored_rewrite_rules()['random/?$'] ?? null, 'The post type archive should be reachable at the random base.' );
     1187        }
     1188
     1189        /**
     1190         * Data provider.
     1191         *
     1192         * @return array<string, array{0: string, 1: array<string, mixed>}>
     1193         */
     1194        public static function data_random_root_rule_is_omitted_for_a_post_type_archive_at_the_random_base(): array {
     1195                return array(
     1196                        'post type named random' => array(
     1197                                'random',
     1198                                array(
     1199                                        'public'      => true,
     1200                                        'has_archive' => true,
     1201                                ),
     1202                        ),
     1203                        'archive slug random'    => array(
     1204                                'wptests_random',
     1205                                array(
     1206                                        'public'      => true,
     1207                                        'has_archive' => 'random',
     1208                                ),
     1209                        ),
     1210                        'rewrite slug random'    => array(
     1211                                'wptests_random',
     1212                                array(
     1213                                        'public'      => true,
     1214                                        'has_archive' => true,
     1215                                        'rewrite'     => array( 'slug' => 'random' ),
     1216                                ),
     1217                        ),
     1218                );
     1219        }
     1220
     1221        /**
     1222         * Ensure a post type archive with a slug similar to the random base does not take it.
     1223         *
     1224         * @dataProvider data_random_root_rule_is_kept_for_a_post_type_archive_with_a_similar_slug
     1225         *
     1226         * @param string $slug Post type archive slug.
     1227         */
     1228        public function test_random_root_rule_is_kept_for_a_post_type_archive_with_a_similar_slug( string $slug ): void {
     1229                register_post_type(
     1230                        'wptests_similar',
     1231                        array(
     1232                                'public'      => true,
     1233                                'has_archive' => $slug,
     1234                        )
     1235                );
     1236
     1237                $this->get_random_rewrite_rules();
     1238
     1239                $this->assertSame( 'index.php?random=1', $this->get_stored_rewrite_rules()['random/?$'] ?? null, "The root rule should be kept for a post type archive at {$slug}." );
     1240        }
     1241
     1242        /**
     1243         * Data provider.
     1244         *
     1245         * @return array<string, string[]>
     1246         */
     1247        public static function data_random_root_rule_is_kept_for_a_post_type_archive_with_a_similar_slug(): array {
     1248                return array(
     1249                        'suffixed'   => array( 'randomful' ),
     1250                        'hyphenated' => array( 'random-ful' ),
     1251                        'prefixed'   => array( 'not-random' ),
     1252                );
     1253        }
     1254
     1255        /**
     1256         * Ensure the root rule is omitted when a top rule uses its regex with a custom random base.
     1257         *
     1258         * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
     1259         */
     1260        public function test_random_root_rule_is_omitted_when_a_top_rule_uses_its_regex_with_a_custom_random_base(): void {
     1261                global $wp_rewrite;
     1262
     1263                $wp_rewrite->random_base = 'surprise-me';
     1264                add_rewrite_rule( 'surprise-me/?$', 'index.php?pagename=wptests-taken', 'top' );
     1265
     1266                $this->get_random_rewrite_rules();
     1267
     1268                $this->assertSame( 'index.php?pagename=wptests-taken', $this->get_stored_rewrite_rules()['surprise-me/?$'] ?? null, 'The top rule should take precedence over the root rule for the custom random base.' );
     1269        }
     1270
     1271        /**
     1272         * Ensure a top rule at the default random base does not replace the root rule for a custom random base.
     1273         *
     1274         * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
     1275         */
     1276        public function test_random_root_rule_is_kept_when_a_top_rule_uses_the_default_base_with_a_custom_random_base(): void {
     1277                global $wp_rewrite;
     1278
     1279                $wp_rewrite->random_base = 'surprise-me';
     1280                add_rewrite_rule( 'random/?$', 'index.php?pagename=wptests-taken', 'top' );
     1281
     1282                $this->get_random_rewrite_rules();
     1283
     1284                $this->assertSame( 'index.php?random=1', $this->get_stored_rewrite_rules()['surprise-me/?$'] ?? null, 'The root rule for the custom random base should be kept when a top rule only uses the default random base.' );
     1285        }
     1286
     1287        /**
     1288         * Ensure the root rule is omitted when a top rule uses its regex with PATHINFO permalinks.
     1289         */
     1290        public function test_random_root_rule_is_omitted_when_a_top_rule_uses_its_regex_with_pathinfo_permalinks(): void {
     1291                $this->set_permalink_structure( '/index.php/%postname%/' );
     1292                create_initial_taxonomies();
     1293
     1294                add_rewrite_rule( 'index.php/random/?$', 'index.php?pagename=wptests-taken', 'top' );
     1295
     1296                $this->get_random_rewrite_rules();
     1297
     1298                $this->assertSame( 'index.php?pagename=wptests-taken', $this->get_stored_rewrite_rules()['index.php/random/?$'] ?? null, 'The top rule should take precedence over the root rule with PATHINFO permalinks.' );
     1299        }
    10571300}
Note: See TracChangeset for help on using the changeset viewer.