diff --git a/includes/class-opml.php b/includes/class-opml.php index 50b318f..983a1c1 100644 --- a/includes/class-opml.php +++ b/includes/class-opml.php @@ -78,29 +78,19 @@ public static function register() { * to, so they need rules of their own. */ public static function add_rewrite_rules() { - foreach ( \array_filter( self::well_known_rules() ) as $regex => $query ) { + foreach ( \array_filter( self::rewrite_rules() ) as $regex => $query ) { \add_rewrite_rule( $regex, $query, 'top' ); } - // A `.opml` suffix as an alias for `?opml` on pages, so the URL - // looks like a file: /blogroll.opml. The query var stays the - // canonical form; it also works with plain permalinks. - // This rule matches the well-known path too. Rules keep the order - // they were added in, so the well-known rules above have to stay - // above; the well-known tests cover that. - \add_rewrite_rule( - '^(.+?)\\.opml$', - \sprintf( 'index.php?pagename=$matches[1]&%s=', self::QUERY_VAR ), - 'top' - ); } /** - * The rewrite rules of the well-known URLs. + * The rewrite rules of the plugin: the well-known URLs and the `.opml` + * suffix. * * @return array Regex => query, or => null for a URL this plugin * does not serve on this site. */ - private static function well_known_rules() { + private static function rewrite_rules() { $rules = array(); foreach ( self::FILES as $value => $file ) { // Only the files with a well-known path need a rule. @@ -115,12 +105,21 @@ private static function well_known_rules() { ? null : \sprintf( 'index.php?%s=%s', self::QUERY_VAR, $value ); } + + // A `.opml` suffix as an alias for `?opml` on pages, so the URL + // looks like a file: /blogroll.opml. The query var stays the + // canonical form; it also works with plain permalinks. Nothing + // under /.well-known/ is a page, and the files there belong to + // whoever serves them, SocialSpec for example, whose rule comes + // after this one. + $rules['^(?!\\.well-known/)(.+?)\\.opml$'] = \sprintf( 'index.php?pagename=$matches[1]&%s=', self::QUERY_VAR ); + return $rules; } /** - * Flush the rewrite rules when the stored well-known rules are out of - * date. + * Flush the rewrite rules when the stored rules of the plugin are out + * of date. * * Activation flushes, an update does not, so a new well-known URL * would stay a 404 on existing sites. The rules also change when @@ -134,7 +133,7 @@ public static function flush_stale_rewrite_rules() { return; } - foreach ( self::well_known_rules() as $regex => $query ) { + foreach ( self::rewrite_rules() as $regex => $query ) { if ( ( $stored[ $regex ] ?? null ) !== $query ) { \flush_rewrite_rules( false ); return; diff --git a/tests/test-opml.php b/tests/test-opml.php index 31d188f..47a0672 100644 --- a/tests/test-opml.php +++ b/tests/test-opml.php @@ -317,6 +317,30 @@ public function test_socialspec_following_adds_the_blogroll_feeds_once() { ); } + public function test_opml_suffix_leaves_well_known_files_of_others_alone() { + global $wp_rewrite; + $regex = '^\.well-known/(other\.opml)$'; + add_filter( + 'query_vars', + function ( $vars ) { + $vars[] = 'well-known'; + return $vars; + } + ); + // Not /%postname%/: that one makes WordPress check that a page + // exists before it takes a page rule, which hides the problem. + $this->set_permalink_structure( '/%year%/%monthnum%/%postname%/' ); + // Added after this plugin's rules, the way SocialSpec adds its own. + add_rewrite_rule( $regex, 'index.php?well-known=$matches[1]', 'top' ); + flush_rewrite_rules( false ); + + $this->go_to( home_url( '/.well-known/other.opml' ) ); + $this->assertSame( 'other.opml', get_query_var( 'well-known' ) ); + + unset( $wp_rewrite->extra_rules_top[ $regex ] ); + $this->set_permalink_structure( '' ); + } + public function test_opml_suffix_is_an_alias_for_the_query_var() { $id = self::factory()->post->create( array(