REST API: Support . in theme directory names in WP_REST_Global_Styles_Controller, WP_REST_Templates_Controller, and WP_REST_Themes_Controller.

Regex changes from [52376] are reverted to restore the original regex patterns. Why? [52376] used an include characters pattern, which was too limiting. It did not account for localized characters, such as `é`, or other valid directory name characters.

The original theme directory regex pattern, i.e. `[^.\/]+(?:\/[^.\/]+)?` excluded the period `.` character. Removing the `.` character resolves the reported issue by allowing matching for `themes/theme-dirname-1.0/` or `themes/<subdirname>/theme-dirname-1.0/`.

As the pattern used an exclude approach, all characters are valid for matching except for `/`. However, not all characters are cross-platform valid for directory names. For example, the characters `/:<>*?"|` are not valid on Windows OS. The pattern now excludes those characters.

The theme's directory (or subdirectory) name pattern matching is now used in `WP_REST_Global_Styles_Controller`, `WP_REST_Templates_Controller`, and `WP_REST_Themes_Controller`.

Follow-up to [51003], [52051], [52275], [52376].

Props costdev, hellofromTonya, spacedmonkey, TimothyBlynJacobs, bijayyadav, kafleg.
Fixes #54596.

git-svn-id: https://develop.svn.wordpress.org/trunk@52399 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Tonya Mork 2021-12-21 04:12:06 +00:00
parent b161cfc1ff
commit e1f5600c15
18 changed files with 493 additions and 48 deletions

View File

@ -41,7 +41,14 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
// List themes global styles.
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)',
// The route.
sprintf(
'/%s/themes/(?P<stylesheet>%s)',
$this->rest_base,
// Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
// Excludes invalid directory name characters: `/:<>*?"|`.
'[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?'
),
array(
array(
'methods' => WP_REST_Server::READABLE,
@ -61,7 +68,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
// Lists/updates a single global style variation based on the given id.
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
array(
array(
'methods' => WP_REST_Server::READABLE,
@ -88,8 +95,8 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
/**
* Sanitize the global styles ID or stylesheet to decode endpoint.
* For example, `wp/v2/global-styles/templatetwentytwo%200.4.0`
* would be decoded to `templatetwentytwo 0.4.0`.
* For example, `wp/v2/global-styles/twentytwentytwo%200.4.0`
* would be decoded to `twentytwentytwo 0.4.0`.
*
* @since 5.9.0
*

View File

@ -68,7 +68,16 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
// Lists/updates a single template based on the given id.
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
// The route.
sprintf(
'/%s/(?P<id>%s%s)',
$this->rest_base,
// Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
// Excludes invalid directory name characters: `/:<>*?"|`.
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
// Matches the template name.
'[\/\w-]+'
),
array(
'args' => array(
'id' => array(
@ -149,7 +158,6 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
* @return string Sanitized template ID.
*/
public function _sanitize_template_id( $id ) {
// Decode empty space.
$id = urldecode( $id );
$last_slash_pos = strrpos( $id, '/' );

View File

@ -16,7 +16,11 @@
*/
class WP_REST_Themes_Controller extends WP_REST_Controller {
const PATTERN = '[^.\/]+(?:\/[^.\/]+)?';
/**
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
* Excludes invalid directory name characters: `/:<>*?"|`.
*/
const PATTERN = '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?';
/**
* Constructor.
@ -56,8 +60,9 @@ class WP_REST_Themes_Controller extends WP_REST_Controller {
array(
'args' => array(
'stylesheet' => array(
'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ),
'type' => 'string',
'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ),
'type' => 'string',
'sanitize_callback' => array( $this, '_sanitize_stylesheet_callback' ),
),
),
array(
@ -70,6 +75,18 @@ class WP_REST_Themes_Controller extends WP_REST_Controller {
);
}
/**
* Sanitize the stylesheet to decode endpoint.
*
* @since 5.9.0
*
* @param string $stylesheet The stylesheet name.
* @return string Sanitized stylesheet.
*/
public function _sanitize_stylesheet_callback( $stylesheet ) {
return urldecode( $stylesheet );
}
/**
* Checks if a given request has access to read the theme.
*

View File

@ -0,0 +1,4 @@
<?php
/**
* Block theme.
*/

View File

@ -0,0 +1,3 @@
<?php
echo 'PHP template for page with ID 1';

View File

@ -0,0 +1,3 @@
<!-- wp:paragraph -->
<p>Large Héader Témplaté Part</p>
<!-- /wp:paragraph -->

View File

@ -0,0 +1,7 @@
/*
Theme Name: Block Theme [0.4.0]
Theme URI: https://wordpress.org/
Description: Has different characters in theme directory name for testing purposes.
Version: 0.4.0
Text Domain: block-theme
*/

View File

@ -0,0 +1,9 @@
<!-- wp:template-part {"slug":"header-large-dark","tagName":"header"} /-->
<!-- wp:group {"tagName":"main"} -->
<main class="wp-block-group">
<!-- wp:post-content {"layout":{"inherit":true}} /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

View File

@ -0,0 +1,71 @@
{
"version": 1,
"settings": {
"color": {
"palette": [
{
"slug": "light",
"name": "Light",
"color": "#f5f7f9"
},
{
"slug": "dark",
"name": "Dark",
"color": "#000"
}
],
"gradients": [
{
"name": "Custom gradient",
"gradient": "linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)",
"slug": "custom-gradient"
}
],
"custom": false,
"customGradient": false
},
"typography": {
"fontSizes": [
{
"name": "Custom",
"slug": "custom",
"size": "100px"
}
],
"customFontSize": false,
"customLineHeight": true
},
"spacing": {
"units": [
"rem"
],
"customPadding": true
},
"blocks": {
"core/paragraph": {
"color": {
"palette": [
{
"slug": "light",
"name": "Light",
"color": "#f5f7f9"
}
]
}
}
}
},
"customTemplates": [
{
"name": "page-home",
"title": "Homepage template"
}
],
"templateParts": [
{
"name": "small-header",
"title": "Small Header",
"area": "header"
}
]
}

View File

@ -0,0 +1,4 @@
<?php
/**
* Block theme.
*/

View File

@ -0,0 +1,7 @@
/*
Theme Name: Block Theme [1.0.0] in subdirectory
Theme URI: https://wordpress.org/
Description: Has different characters in theme directory name for testing purposes.
Version: 0.4.0
Text Domain: block-theme
*/

View File

@ -0,0 +1,71 @@
{
"version": 1,
"settings": {
"color": {
"palette": [
{
"slug": "light",
"name": "Light",
"color": "#f5f7f9"
},
{
"slug": "dark",
"name": "Dark",
"color": "#000"
}
],
"gradients": [
{
"name": "Custom gradient",
"gradient": "linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)",
"slug": "custom-gradient"
}
],
"custom": false,
"customGradient": false
},
"typography": {
"fontSizes": [
{
"name": "Custom",
"slug": "custom",
"size": "100px"
}
],
"customFontSize": false,
"customLineHeight": true
},
"spacing": {
"units": [
"rem"
],
"customPadding": true
},
"blocks": {
"core/paragraph": {
"color": {
"palette": [
{
"slug": "light",
"name": "Light",
"color": "#f5f7f9"
}
]
}
}
}
},
"customTemplates": [
{
"name": "page-home",
"title": "Homepage template"
}
],
"templateParts": [
{
"name": "small-header",
"title": "Small Header",
"area": "header"
}
]
}

View File

@ -98,23 +98,23 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
public function test_register_routes() {
$routes = rest_get_server()->get_routes();
$this->assertArrayHasKey(
'/wp/v2/global-styles/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/global-styles/(?P<id>[\/\w-]+)',
$routes,
'Single global style based on the given ID route does not exist'
);
$this->assertCount(
2,
$routes['/wp/v2/global-styles/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)'],
$routes['/wp/v2/global-styles/(?P<id>[\/\w-]+)'],
'Single global style based on the given ID route does not have exactly two elements'
);
$this->assertArrayHasKey(
'/wp/v2/global-styles/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/global-styles/themes/(?P<stylesheet>[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
$routes,
'Theme global styles route does not exist'
);
$this->assertCount(
1,
$routes['/wp/v2/global-styles/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)'],
$routes['/wp/v2/global-styles/themes/(?P<stylesheet>[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)'],
'Theme global styles route does not have exactly one element'
);
}
@ -164,14 +164,17 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
* @dataProvider data_get_theme_item_invalid_theme_dirname
* @covers WP_REST_Global_Styles_Controller::get_theme_item
* @ticket 54596
*
* @param string $theme_dirname Theme directory to test.
* @param string $expected Expected error code.
*/
public function test_get_theme_item_invalid_theme_dirname( $theme_dirname ) {
public function test_get_theme_item_invalid_theme_dirname( $theme_dirname, $expected ) {
wp_set_current_user( self::$admin_id );
switch_theme( $theme_dirname );
$request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/' . $theme_dirname );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
$this->assertErrorResponse( $expected, $response, 404 );
}
/**
@ -181,12 +184,39 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
*/
public function data_get_theme_item_invalid_theme_dirname() {
return array(
'with |' => array( 'my|theme' ),
'with +' => array( 'my+theme' ),
'with {}' => array( 'my{theme}' ),
'with #' => array( 'my#theme' ),
'with !' => array( 'my!theme' ),
'multiple invalid chars' => array( 'mytheme-[_(+@)]#! v4.0' ),
'+' => array(
'theme_dirname' => 'my+theme+',
'expected' => 'rest_theme_not_found',
),
':' => array(
'theme_dirname' => 'my:theme:',
'expected' => 'rest_no_route',
),
'<>' => array(
'theme_dirname' => 'my<theme>',
'expected' => 'rest_no_route',
),
'*' => array(
'theme_dirname' => 'my*theme*',
'expected' => 'rest_no_route',
),
'?' => array(
'theme_dirname' => 'my?theme?',
'expected' => 'rest_no_route',
),
'"' => array(
'theme_dirname' => 'my"theme?"',
'expected' => 'rest_no_route',
),
'| (invalid on Windows)' => array(
'theme_dirname' => 'my|theme|',
'expected' => 'rest_no_route',
),
// Themes deep in subdirectories.
'2 subdirectories deep' => array(
'theme_dirname' => 'subdir/subsubdir/mytheme',
'expected' => 'rest_global_styles_not_found',
),
);
}
@ -194,6 +224,8 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
* @dataProvider data_get_theme_item
* @covers WP_REST_Global_Styles_Controller::get_theme_item
* @ticket 54596
*
* @param string $theme Theme directory to test.
*/
public function test_get_theme_item( $theme ) {
wp_set_current_user( self::$admin_id );
@ -216,18 +248,34 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
*/
public function data_get_theme_item() {
return array(
'alphabetic chars' => array( 'mytheme' ),
'alphanumeric chars' => array( 'mythemev1' ),
'space' => array( 'my theme' ),
'-' => array( 'my-theme' ),
'_' => array( 'my_theme' ),
'.' => array( 'mytheme0.1' ),
'- and .' => array( 'my-theme-0.1' ),
'space and .' => array( 'mytheme v0.1' ),
'space, -, _, .' => array( 'my-theme-v0.1' ),
'[]' => array( 'my[theme]' ),
'()' => array( 'my(theme)' ),
'@' => array( 'my@theme' ),
'alphabetic' => array( 'mytheme' ),
'alphanumeric' => array( 'mythemev1' ),
'àáâãäåæç' => array( 'àáâãäåæç' ),
'space' => array( 'my theme' ),
'-_.' => array( 'my_theme-0.1' ),
'[]' => array( 'my[theme]' ),
'()' => array( 'my(theme)' ),
'{}' => array( 'my{theme}' ),
'&=#@!$,^~%' => array( 'theme &=#@!$,^~%' ),
'all combined' => array( 'thémé {}&=@!$,^~%[0.1](-_-)' ),
// Themes in a subdirectory.
'subdir: alphabetic' => array( 'subdir/mytheme' ),
'subdir: alphanumeric in theme' => array( 'subdir/mythemev1' ),
'subdir: alphanumeric in subdir' => array( 'subdirv1/mytheme' ),
'subdir: alphanumeric in both' => array( 'subdirv1/mythemev1' ),
'subdir: àáâãäåæç in theme' => array( 'subdir/àáâãäåæç' ),
'subdir: àáâãäåæç in subdir' => array( 'àáâãäåæç/mythemev1' ),
'subdir: àáâãäåæç in both' => array( 'àáâãäåæç/àáâãäåæç' ),
'subdir: space in theme' => array( 'subdir/my theme' ),
'subdir: space in subdir' => array( 'sub dir/mytheme' ),
'subdir: space in both' => array( 'sub dir/my theme' ),
'subdir: -_. in theme' => array( 'subdir/my_theme-0.1' ),
'subdir: -_. in subdir' => array( 'sub_dir-0.1/mytheme' ),
'subdir: -_. in both' => array( 'sub_dir-0.1/my_theme-0.1' ),
'subdir: all combined in theme' => array( 'subdir/thémé {}&=@!$,^~%[0.1](-_-)' ),
'subdir: all combined in subdir' => array( 'sűbdīr {}&=@!$,^~%[0.1](-_-)/mytheme' ),
'subdir: all combined in both' => array( 'sűbdīr {}&=@!$,^~%[0.1](-_-)/thémé {}&=@!$,^~%[0.1](-_-)' ),
);
}

View File

@ -135,8 +135,8 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
'/wp/v2/users/(?P<user_id>(?:[\\d]+|me))/application-passwords/(?P<uuid>[\\w\\-]+)',
'/wp/v2/comments',
'/wp/v2/comments/(?P<id>[\\d]+)',
'/wp/v2/global-styles/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/global-styles/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/global-styles/(?P<id>[\/\w-]+)',
'/wp/v2/global-styles/themes/(?P<stylesheet>[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
'/wp/v2/search',
'/wp/v2/block-renderer/(?P<name>[a-z0-9-]+/[a-z0-9-]+)',
'/wp/v2/block-types',
@ -144,19 +144,19 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
'/wp/v2/block-types/(?P<namespace>[a-zA-Z0-9_-]+)/(?P<name>[a-zA-Z0-9_-]+)',
'/wp/v2/settings',
'/wp/v2/template-parts',
'/wp/v2/template-parts/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/template-parts/(?P<id>[\d]+)/autosaves',
'/wp/v2/template-parts/(?P<id>([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
'/wp/v2/template-parts/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)',
'/wp/v2/template-parts/(?P<parent>[\d]+)/revisions',
'/wp/v2/template-parts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)',
'/wp/v2/templates',
'/wp/v2/templates/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/templates/(?P<id>[\d]+)/autosaves',
'/wp/v2/templates/(?P<id>([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
'/wp/v2/templates/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)',
'/wp/v2/templates/(?P<parent>[\d]+)/revisions',
'/wp/v2/templates/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)',
'/wp/v2/themes',
'/wp/v2/themes/(?P<stylesheet>[^.\/]+(?:\/[^.\/]+)?)',
'/wp/v2/themes/(?P<stylesheet>[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
'/wp/v2/plugins',
'/wp/v2/plugins/(?P<plugin>[^.\/]+(?:\/[^.\/]+)?)',
'/wp/v2/block-directory/search',

View File

@ -1285,15 +1285,86 @@ class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase {
}
/**
* @ticket 54349
* @dataProvider data_get_item_non_subdir_theme
* @ticket 54596
* @covers WP_REST_Themes_Controller::get_item
*
* @param string $theme_dir Theme directory to test.
* @param string $expected_name Expected theme name.
*/
public function test_get_item_subdirectory_theme() {
public function test_get_item_non_subdir_theme( $theme_dir, $expected_name ) {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', self::$themes_route . '/subdir/theme2' );
$request = new WP_REST_Request( 'GET', self::$themes_route . $theme_dir );
$response = rest_do_request( $request );
$this->assertSame( 200, $response->get_status() );
$this->assertSame( 'My Subdir Theme', $response->get_data()['name']['raw'] );
$this->assertSame( $expected_name, $response->get_data()['name']['raw'] );
}
/**
* Data provider.
*
* @return array
*/
public function data_get_item_non_subdir_theme() {
return array(
'parent theme' => array(
'theme_dir' => '/block-theme',
'expected_name' => 'Block Theme',
),
'child theme' => array(
'theme_dir' => '/block-theme-child',
'expected_name' => 'Block Theme Child Theme',
),
'theme with _-[]. characters' => array(
'theme_dir' => '/block_theme-[0.4.0]',
'expected_name' => 'Block Theme [0.4.0]',
),
);
}
/**
* @dataProvider data_get_item_subdirectory_theme
* @ticket 54349
* @ticket 54596
* @covers WP_REST_Themes_Controller::get_item
*
* @param string $theme_dir Theme directory to test.
* @param string $expected_name Expected theme name.
*/
public function test_get_item_subdirectory_theme( $theme_dir, $expected_name ) {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', self::$themes_route . $theme_dir );
$response = rest_do_request( $request );
$this->assertSame(
200,
$response->get_status(),
'A 200 OK status was not returned.'
);
$this->assertSame(
$expected_name,
$response->get_data()['name']['raw'],
'The actual theme name was not the expected theme name.'
);
}
/**
* Data provider.
*
* @return array
*/
public function data_get_item_subdirectory_theme() {
return array(
'theme2' => array(
'theme_dir' => '/subdir/theme2',
'expected_name' => 'My Subdir Theme',
),
'theme with _-[]. characters' => array(
'theme_dir' => '/subdir/block_theme-[1.0.0]',
'expected_name' => 'Block Theme [1.0.0] in subdirectory',
),
);
}
/**

View File

@ -65,7 +65,7 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
'Templates route does not exist'
);
$this->assertArrayHasKey(
'/wp/v2/templates/(?P<id>[\/\s%\w\.\(\)\[\]\@_\-]+)',
'/wp/v2/templates/(?P<id>([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
$routes,
'Single template based on the given ID route does not exist'
);
@ -208,6 +208,119 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
);
}
/**
* @dataProvider data_get_item_with_valid_theme_dirname
* @covers WP_REST_Templates_Controller::get_item
* @ticket 54596
*
* @param string $theme_dir Theme directory to test.
* @param string $template Template to test.
* @param array $args Arguments to create the 'wp_template" post.
*/
public function test_get_item_with_valid_theme_dirname( $theme_dir, $template, array $args ) {
wp_set_current_user( self::$admin_id );
switch_theme( $theme_dir );
// Set up template post.
$args['post_type'] = 'wp_template';
$args['tax_input'] = array(
'wp_theme' => array(
get_stylesheet(),
),
);
$post = self::factory()->post->create_and_get( $args );
wp_set_post_terms( $post->ID, get_stylesheet(), 'wp_theme' );
$request = new WP_REST_Request( 'GET', "/wp/v2/templates/{$theme_dir}//{$template}" );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
unset( $data['content'] );
unset( $data['_links'] );
$this->assertSameSetsWithIndex(
array(
'id' => "{$theme_dir}//{$template}",
'theme' => $theme_dir,
'slug' => $template,
'source' => 'custom',
'origin' => null,
'type' => 'wp_template',
'description' => $args['post_excerpt'],
'title' => array(
'raw' => $args['post_title'],
'rendered' => $args['post_title'],
),
'status' => 'publish',
'wp_id' => $post->ID,
'has_theme_file' => false,
'is_custom' => true,
'author' => self::$admin_id,
),
$data
);
}
/**
* Data provider.
*
* @return array
*/
public function data_get_item_with_valid_theme_dirname() {
$theme_root_dir = DIR_TESTDATA . '/themedir1/';
return array(
'template parts: parent theme' => array(
'theme_dir' => 'themedir1/block-theme',
'template' => 'small-header',
'args' => array(
'post_name' => 'small-header',
'post_title' => 'Small Header Template',
'post_content' => file_get_contents( $theme_root_dir . '/block-theme/parts/small-header.html' ),
'post_excerpt' => 'Description of small header template.',
),
),
'template: parent theme' => array(
'theme_dir' => 'themedir1/block-theme',
'template' => 'page-home',
'args' => array(
'post_name' => 'page-home',
'post_title' => 'Home Page Template',
'post_content' => file_get_contents( $theme_root_dir . 'block-theme/templates/page-home.html' ),
'post_excerpt' => 'Description of page home template.',
),
),
'template: child theme' => array(
'theme_dir' => 'themedir1/block-theme-child',
'template' => 'page-1',
'args' => array(
'post_name' => 'page-1',
'post_title' => 'Page 1 Template',
'post_content' => file_get_contents( $theme_root_dir . 'block-theme-child/templates/page-1.html' ),
'post_excerpt' => 'Description of page 1 template.',
),
),
'template part: subdir with _-[]. characters' => array(
'theme_dir' => 'themedir1/block_theme-[0.4.0]',
'template' => 'large-header',
'args' => array(
'post_name' => 'large-header',
'post_title' => 'Large Header Template Part',
'post_content' => file_get_contents( $theme_root_dir . 'block_theme-[0.4.0]/parts/large-header.html' ),
'post_excerpt' => 'Description of large header template.',
),
),
'template: subdir with _-[]. characters' => array(
'theme_dir' => 'themedir1/block_theme-[0.4.0]',
'template' => 'page-large-header',
'args' => array(
'post_name' => 'page-large-header',
'post_title' => 'Page Large Template',
'post_content' => file_get_contents( $theme_root_dir . 'block_theme-[0.4.0]/templates/page-large-header.html' ),
'post_excerpt' => 'Description of page large template.',
),
),
);
}
/**
* @ticket 54507
* @dataProvider get_template_ids_to_sanitize

View File

@ -163,6 +163,8 @@ class Tests_Theme_ThemeDir extends WP_UnitTestCase {
'REST Theme',
'Block Theme',
'Block Theme Child Theme',
'Block Theme [0.4.0]',
'Block Theme [1.0.0] in subdirectory',
);
sort( $theme_names );

View File

@ -5140,7 +5140,7 @@ mockedApiResponse.Schema = {
]
}
},
"/wp/v2/templates/(?P<id>[\\/\\s%\\w\\.\\(\\)\\[\\]\\@_\\-]+)": {
"/wp/v2/templates/(?P<id>([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w-]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
@ -5792,7 +5792,7 @@ mockedApiResponse.Schema = {
]
}
},
"/wp/v2/template-parts/(?P<id>[\\/\\s%\\w\\.\\(\\)\\[\\]\\@_\\-]+)": {
"/wp/v2/template-parts/(?P<id>([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w-]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
@ -9424,7 +9424,7 @@ mockedApiResponse.Schema = {
}
]
},
"/wp/v2/global-styles/themes/(?P<stylesheet>[\\/\\s%\\w\\.\\(\\)\\[\\]\\@_\\-]+)": {
"/wp/v2/global-styles/themes/(?P<stylesheet>[^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)": {
"namespace": "wp/v2",
"methods": [
"GET"
@ -9444,7 +9444,7 @@ mockedApiResponse.Schema = {
}
]
},
"/wp/v2/global-styles/(?P<id>[\\/\\s%\\w\\.\\(\\)\\[\\]\\@_\\-]+)": {
"/wp/v2/global-styles/(?P<id>[\\/\\w-]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
@ -9668,7 +9668,7 @@ mockedApiResponse.Schema = {
"self": "http://example.org/index.php?rest_route=/wp/v2/themes"
}
},
"/wp/v2/themes/(?P<stylesheet>[^.\\/]+(?:\\/[^.\\/]+)?)": {
"/wp/v2/themes/(?P<stylesheet>[^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)": {
"namespace": "wp/v2",
"methods": [
"GET"