From bb491e07e90e27748c15a8af3aec3e54d74d7dc9 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 26 Nov 2021 16:06:36 +0100 Subject: [PATCH] [REST] Restore the missing double slash in the ID received by /templates (#36881) * Fall back to a double-slashed template name if a single-slashed one is missing * Add a unit test * Use $_SERVER['REQUEST_URI'] as a fallback instead of simply replacing all slashes with a double slash. * Fallback to parsing the query string instead of doing it by default * Code style * Update docstring * Try doubling the last slash instead of parsing REQUEST_URI * Use sanitize_callback * Lint * Test for both single and double slash * Add unit tests for _sanitize_template_id * Update phpunit/class-gutenberg-rest-template-controller-test.php * Update phpunit/class-gutenberg-rest-template-controller-test.php --- ...ss-gutenberg-rest-templates-controller.php | 35 +++++++++- ...utenberg-rest-template-controller-test.php | 70 +++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php index eb7f047a8e98d..65343e8118bb6 100644 --- a/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php +++ b/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php @@ -67,8 +67,9 @@ public function register_routes() { 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'id' => array( - 'description' => __( 'The id of a template', 'gutenberg' ), - 'type' => 'string', + 'description' => __( 'The id of a template', 'gutenberg' ), + 'type' => 'string', + 'sanitize_callback' => array( $this, '_sanitize_template_id' ), ), ), ), @@ -116,6 +117,36 @@ protected function permissions_check() { return true; } + /** + * Requesting this endpoint for a template like "twentytwentytwo//home" requires using + * a path like /wp/v2/templates/twentytwentytwo//home. There are special cases when + * WordPress routing corrects the name to contain only a single slash like "twentytwentytwo/home". + * + * This method doubles the last slash if it's not already doubled. It relies on the template + * ID format {theme_name}//{template_slug} and the fact that slugs cannot contain slashes. + * + * See https://core.trac.wordpress.org/ticket/54507 for more context + * + * @param string $id Template ID. + * @return string Sanitized template ID. + */ + public function _sanitize_template_id( $id ) { + $last_slash_pos = strrpos( $id, '/' ); + if ( false === $last_slash_pos ) { + return $id; + } + + $is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/'; + if ( $is_double_slashed ) { + return $id; + } + return ( + substr( $id, 0, $last_slash_pos ) + . '/' + . substr( $id, $last_slash_pos ) + ); + } + /** * Checks if a given request has access to read templates. * diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index 4fc3b5407e06e..6787f4c0ab8ff 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -160,6 +160,76 @@ public function test_get_item() { ); } + /** + * Ticket 54507 + * + * @dataProvider get_template_endpoint_urls + */ + public function test_get_item_works_with_a_single_slash( $endpoint_url ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', $endpoint_url ); + $response = rest_get_server()->dispatch( $request ); + + $data = $response->get_data(); + unset( $data['content'] ); + unset( $data['_links'] ); + + $this->assertEquals( + array( + 'id' => 'tt1-blocks//index', + 'theme' => 'tt1-blocks', + 'slug' => 'index', + 'title' => array( + 'raw' => 'Index', + 'rendered' => 'Index', + ), + 'description' => 'The default template used when no other template is available. This is a required template in WordPress.', + 'status' => 'publish', + 'source' => 'theme', + 'type' => 'wp_template', + 'wp_id' => null, + 'has_theme_file' => true, + ), + $data + ); + } + + /** + * + */ + public function get_template_endpoint_urls() { + return array( + array( '/wp/v2/templates/tt1-blocks/index' ), + array( '/wp/v2/templates/tt1-blocks//index' ), + ); + } + + /** + * Ticket 54507 + * + * @dataProvider get_template_ids_to_sanitize + */ + public function test_sanitize_template_id( $input_id, $sanitized_id ) { + $endpoint = new Gutenberg_REST_Templates_Controller( 'wp_template' ); + $this->assertEquals( + $sanitized_id, + $endpoint->_sanitize_template_id( $input_id ) + ); + } + + /** + * + */ + public function get_template_ids_to_sanitize() { + return array( + array( 'tt1-blocks/index', 'tt1-blocks//index' ), + array( 'tt1-blocks//index', 'tt1-blocks//index' ), + + array( 'theme-experiments/tt1-blocks/index', 'theme-experiments/tt1-blocks//index' ), + array( 'theme-experiments/tt1-blocks//index', 'theme-experiments/tt1-blocks//index' ), + ); + } + public function test_create_item() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/templates' );