From 70e7381ad7eba36bb785a8d4449303a20e85550d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 25 Nov 2021 17:52:25 +0100 Subject: [PATCH 01/13] Fall back to a double-slashed template name if a single-slashed one is missing --- ...ss-gutenberg-rest-templates-controller.php | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 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 eb7f047a8e98df..0cd51e9e61f0fb 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 @@ -174,8 +174,11 @@ public function get_item_permissions_check( $request ) { public function get_item( $request ) { if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { $template = get_block_file_template( $request['id'], $this->post_type ); + if ( ! $template ) { + $template = get_block_file_template( str_replace( '/', '//', $request['id'] ), $this->post_type ); + } } else { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + $template = $this->get_template_by_id( $request['id'] ); } if ( ! $template ) { @@ -202,7 +205,7 @@ public function update_item_permissions_check( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + $template = $this->get_template_by_id( $request['id'] ); if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } @@ -223,7 +226,7 @@ public function update_item( $request ) { return $result; } - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + $template = $this->get_template_by_id( $request['id'] ); $fields_update = $this->update_additional_fields_for_object( $template, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; @@ -292,7 +295,7 @@ public function delete_item_permissions_check( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + $template = $this->get_template_by_id( $request['id'] ); if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } @@ -332,6 +335,27 @@ public function delete_item( $request ) { return $this->prepare_item_for_response( $template, $request ); } + /** + * 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 attempts to find a template with a specific ID, and if it's missing then it + * falls back to a double-slashed version of the ID. + * + * See https://core.trac.wordpress.org/ticket/54507 for more context + * + * @param string $id ID of the template. + * @return WP_Block_Template|null Template. + */ + private function get_template_by_id( $id ) { + $template = gutenberg_get_block_template( $id, $this->post_type ); + if ( ! $template ) { + $template = gutenberg_get_block_template( str_replace( '/', '//', $id ), $this->post_type ); + } + return $template; + } + /** * Prepares a single template for create or update. * From 7b7357b22531508b9dbc8d74737fc632ae0d27bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 25 Nov 2021 17:54:45 +0100 Subject: [PATCH 02/13] Add a unit test --- ...utenberg-rest-template-controller-test.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index 4fc3b5407e06e9..b9040732808e32 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -160,6 +160,37 @@ public function test_get_item() { ); } + /** + * Ticket 54507 + */ + public function test_get_item_works_with_a_single_slash() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/tt1-blocks/index' ); + $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 test_create_item() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/templates' ); From 5606e314c7753ec4162b2f375d8ad91bd0789425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 25 Nov 2021 18:46:11 +0100 Subject: [PATCH 03/13] Use $_SERVER['REQUEST_URI'] as a fallback instead of simply replacing all slashes with a double slash. --- ...ss-gutenberg-rest-templates-controller.php | 26 +++++++++++++------ ...utenberg-rest-template-controller-test.php | 11 ++++++-- 2 files changed, 27 insertions(+), 10 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 0cd51e9e61f0fb..198df4f7f97e33 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 @@ -173,10 +173,7 @@ public function get_item_permissions_check( $request ) { */ public function get_item( $request ) { if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { - $template = get_block_file_template( $request['id'], $this->post_type ); - if ( ! $template ) { - $template = get_block_file_template( str_replace( '/', '//', $request['id'] ), $this->post_type ); - } + $template = $this->get_template_by_id( $request['id'], true ); } else { $template = $this->get_template_by_id( $request['id'] ); } @@ -345,13 +342,26 @@ public function delete_item( $request ) { * * See https://core.trac.wordpress.org/ticket/54507 for more context * - * @param string $id ID of the template. + * @param string $id ID of the template. + * @param boolean $from_file Optional Whether to use gutenberg_get_block_template instead of get_block_file_template. * @return WP_Block_Template|null Template. */ - private function get_template_by_id( $id ) { - $template = gutenberg_get_block_template( $id, $this->post_type ); + private function get_template_by_id( $id, $from_file = false ) { + $getter = $from_file ? 'get_block_file_template' : 'gutenberg_get_block_template'; + $template = $getter( $id, $this->post_type ); if ( ! $template ) { - $template = gutenberg_get_block_template( str_replace( '/', '//', $id ), $this->post_type ); + $uri = $_SERVER['REQUEST_URI']; + $delimiter = '/wp/v2/templates/'; + + // Extract part of the path that comes after /wp/v2/templates/. + $literal_id = substr( $uri, strpos( $uri, $delimiter ) + strlen( $delimiter ) ); + + // Extract part of the path before the query string (if any). + $literal_id = explode( '?', $literal_id )[0]; + + // Remove any trailing slashes. + $literal_id = rtrim( $literal_id, '/' ); + $template = $getter( $literal_id, $this->post_type ); } return $template; } diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index b9040732808e32..9a825453115626 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -165,9 +165,16 @@ public function test_get_item() { */ public function test_get_item_works_with_a_single_slash() { wp_set_current_user( self::$admin_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/templates/tt1-blocks/index' ); + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/tt1-blocks/index' ); + $old_uri = $_SERVER['REQUEST_URI']; + + $_SERVER['REQUEST_URI'] = '/index.php/wp-json/wp/v2/templates/tt1-blocks//index/?context=edit&_locale=user'; + $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); + + $_SERVER['REQUEST_URI'] = $old_uri; + + $data = $response->get_data(); unset( $data['content'] ); unset( $data['_links'] ); From 65ccdce2e298bd87dd0351691448fd07d1aa31e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 12:59:07 +0100 Subject: [PATCH 04/13] Fallback to parsing the query string instead of doing it by default --- ...ss-gutenberg-rest-templates-controller.php | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 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 198df4f7f97e33..dbdf3f58b95934 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 @@ -350,18 +350,38 @@ private function get_template_by_id( $id, $from_file = false ) { $getter = $from_file ? 'get_block_file_template' : 'gutenberg_get_block_template'; $template = $getter( $id, $this->post_type ); if ( ! $template ) { - $uri = $_SERVER['REQUEST_URI']; - $delimiter = '/wp/v2/templates/'; + $uri = $_SERVER['REQUEST_URI']; + // Make sure REQUEST_URI is set. + if ( ! $uri ) { + return; + } + $delimiter = $this->namespace . '/' . $this->rest_base . '/'; // Extract part of the path that comes after /wp/v2/templates/. - $literal_id = substr( $uri, strpos( $uri, $delimiter ) + strlen( $delimiter ) ); + $start = strpos( $uri, $delimiter ); + if ( false === $start ) { + return; + } + $inferred_id = substr( $uri, $start + strlen( $delimiter ) ); - // Extract part of the path before the query string (if any). - $literal_id = explode( '?', $literal_id )[0]; + // Only use part of the path until the first querystring-like symbol (either ? or &). + $parts = preg_split( '|[?&]|', $inferred_id ); + if ( ! $parts ) { + return; + } + $inferred_id = $parts[0]; // Remove any trailing slashes. - $literal_id = rtrim( $literal_id, '/' ); - $template = $getter( $literal_id, $this->post_type ); + $inferred_id = rtrim( $inferred_id, '/' ); + + // Halt if the parsed string differs from original one in more than just the number of slashes. + // This prevents any creative inputs that wouldn't otherwise be used. + if ( str_replace( '/', '', $id ) === str_replace( '/', '', $inferred_id ) ) { + return; + } + + // Get the template. + $template = $getter( $inferred_id, $this->post_type ); } return $template; } From 4af2b889cd4d08528e805325d2a5c948ea6fecb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 13:01:32 +0100 Subject: [PATCH 05/13] Code style --- .../class-gutenberg-rest-templates-controller.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 dbdf3f58b95934..da7cbb89eeefe1 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 @@ -350,14 +350,15 @@ private function get_template_by_id( $id, $from_file = false ) { $getter = $from_file ? 'get_block_file_template' : 'gutenberg_get_block_template'; $template = $getter( $id, $this->post_type ); if ( ! $template ) { - $uri = $_SERVER['REQUEST_URI']; // Make sure REQUEST_URI is set. - if ( ! $uri ) { + if ( empty( $_SERVER['REQUEST_URI'] ) ) { return; } + + $uri = $_SERVER['REQUEST_URI']; $delimiter = $this->namespace . '/' . $this->rest_base . '/'; - // Extract part of the path that comes after /wp/v2/templates/. + // Extract the part of the path that comes after /wp/v2/templates/. $start = strpos( $uri, $delimiter ); if ( false === $start ) { return; From 057f769c05c6e515dd1b26fe511276d70330a819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 13:05:03 +0100 Subject: [PATCH 06/13] Update docstring --- .../wordpress-5.9/class-gutenberg-rest-templates-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 da7cbb89eeefe1..3d2d568cd00cf2 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 @@ -338,7 +338,7 @@ public function delete_item( $request ) { * WordPress routing corrects the name to contain only a single slash like "twentytwentytwo/home". * * This method attempts to find a template with a specific ID, and if it's missing then it - * falls back to a double-slashed version of the ID. + * falls back to parsing REQUEST_URI in an attempt to grab the verbatim ID passed by the user. * * See https://core.trac.wordpress.org/ticket/54507 for more context * From 54ca8cd8c6de8f62eeda46b78eae70dbe04d2a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 14:19:21 +0100 Subject: [PATCH 07/13] Try doubling the last slash instead of parsing REQUEST_URI --- ...ss-gutenberg-rest-templates-controller.php | 40 ++++--------------- ...utenberg-rest-template-controller-test.php | 8 +--- 2 files changed, 9 insertions(+), 39 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 3d2d568cd00cf2..07f72b6f134dc5 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 @@ -338,7 +338,8 @@ public function delete_item( $request ) { * WordPress routing corrects the name to contain only a single slash like "twentytwentytwo/home". * * This method attempts to find a template with a specific ID, and if it's missing then it - * falls back to parsing REQUEST_URI in an attempt to grab the verbatim ID passed by the user. + * falls back to doubling the last slash in the identifier. This exploits the fact that template + * ID format is {theme_name}//{template_slug} and that slugs cannot contain slashes. * * See https://core.trac.wordpress.org/ticket/54507 for more context * @@ -350,39 +351,14 @@ private function get_template_by_id( $id, $from_file = false ) { $getter = $from_file ? 'get_block_file_template' : 'gutenberg_get_block_template'; $template = $getter( $id, $this->post_type ); if ( ! $template ) { - // Make sure REQUEST_URI is set. - if ( empty( $_SERVER['REQUEST_URI'] ) ) { - return; - } - - $uri = $_SERVER['REQUEST_URI']; - $delimiter = $this->namespace . '/' . $this->rest_base . '/'; - - // Extract the part of the path that comes after /wp/v2/templates/. - $start = strpos( $uri, $delimiter ); - if ( false === $start ) { - return; - } - $inferred_id = substr( $uri, $start + strlen( $delimiter ) ); - - // Only use part of the path until the first querystring-like symbol (either ? or &). - $parts = preg_split( '|[?&]|', $inferred_id ); - if ( ! $parts ) { - return; - } - $inferred_id = $parts[0]; - - // Remove any trailing slashes. - $inferred_id = rtrim( $inferred_id, '/' ); - - // Halt if the parsed string differs from original one in more than just the number of slashes. - // This prevents any creative inputs that wouldn't otherwise be used. - if ( str_replace( '/', '', $id ) === str_replace( '/', '', $inferred_id ) ) { - return; - } + $last_slash_pos = strrpos( $id, '/' ); + $fallback_id = + substr( $id, 0, $last_slash_pos ) + . '/' + . substr( $id, $last_slash_pos ); // Get the template. - $template = $getter( $inferred_id, $this->post_type ); + $template = $getter( $fallback_id, $this->post_type ); } return $template; } diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index 9a825453115626..534cff4a11df57 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -165,15 +165,9 @@ public function test_get_item() { */ public function test_get_item_works_with_a_single_slash() { wp_set_current_user( self::$admin_id ); - $request = new WP_REST_Request( 'GET', '/wp/v2/templates/tt1-blocks/index' ); - $old_uri = $_SERVER['REQUEST_URI']; - - $_SERVER['REQUEST_URI'] = '/index.php/wp-json/wp/v2/templates/tt1-blocks//index/?context=edit&_locale=user'; - + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/tt1-blocks/index' ); $response = rest_get_server()->dispatch( $request ); - $_SERVER['REQUEST_URI'] = $old_uri; - $data = $response->get_data(); unset( $data['content'] ); unset( $data['_links'] ); From 59cb34af10c6a367ffc31fb4d19eb131a17987d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 14:32:46 +0100 Subject: [PATCH 08/13] Use sanitize_callback --- ...ss-gutenberg-rest-templates-controller.php | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 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 07f72b6f134dc5..a21a57846b5b21 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 @@ -69,6 +69,33 @@ public function register_routes() { 'id' => array( 'description' => __( 'The id of a template', 'gutenberg' ), 'type' => 'string', + + /** + * 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 + */ + 'sanitize_callback' => function( $id ) { + $last_slash_pos = strrpos( $id, '/' ); + if ( $last_slash_pos === false ) { + return $id; + } + + $is_double_slashed = substr($id, $last_slash_pos - 1, 1 ) === '/'; + if ( ! $is_double_slashed ) { + $id = + substr( $id, 0, $last_slash_pos ) + . '/' + . substr( $id, $last_slash_pos ); + } + + return $id; + } ), ), ), @@ -173,9 +200,9 @@ public function get_item_permissions_check( $request ) { */ public function get_item( $request ) { if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { - $template = $this->get_template_by_id( $request['id'], true ); + $template = get_block_file_template( $request['id'], $this->post_type ); } else { - $template = $this->get_template_by_id( $request['id'] ); + $template = gutenberg_get_block_template( $request['id'], $this->post_type ); } if ( ! $template ) { @@ -202,7 +229,7 @@ public function update_item_permissions_check( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { - $template = $this->get_template_by_id( $request['id'] ); + $template = gutenberg_get_block_template( $request['id'], $this->post_type ); if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } @@ -223,7 +250,7 @@ public function update_item( $request ) { return $result; } - $template = $this->get_template_by_id( $request['id'] ); + $template = gutenberg_get_block_template( $request['id'], $this->post_type ); $fields_update = $this->update_additional_fields_for_object( $template, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; @@ -292,7 +319,7 @@ public function delete_item_permissions_check( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { - $template = $this->get_template_by_id( $request['id'] ); + $template = gutenberg_get_block_template( $request['id'], $this->post_type ); if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } @@ -332,37 +359,6 @@ public function delete_item( $request ) { return $this->prepare_item_for_response( $template, $request ); } - /** - * 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 attempts to find a template with a specific ID, and if it's missing then it - * falls back to doubling the last slash in the identifier. This exploits the fact that template - * ID format is {theme_name}//{template_slug} and that slugs cannot contain slashes. - * - * See https://core.trac.wordpress.org/ticket/54507 for more context - * - * @param string $id ID of the template. - * @param boolean $from_file Optional Whether to use gutenberg_get_block_template instead of get_block_file_template. - * @return WP_Block_Template|null Template. - */ - private function get_template_by_id( $id, $from_file = false ) { - $getter = $from_file ? 'get_block_file_template' : 'gutenberg_get_block_template'; - $template = $getter( $id, $this->post_type ); - if ( ! $template ) { - $last_slash_pos = strrpos( $id, '/' ); - $fallback_id = - substr( $id, 0, $last_slash_pos ) - . '/' - . substr( $id, $last_slash_pos ); - - // Get the template. - $template = $getter( $fallback_id, $this->post_type ); - } - return $template; - } - /** * Prepares a single template for create or update. * From 3ff5097dc7dd62b1b1d2436f7519089de1a753da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 14:35:09 +0100 Subject: [PATCH 09/13] Lint --- ...ss-gutenberg-rest-templates-controller.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 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 a21a57846b5b21..d1dab5eb9fa855 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,8 @@ 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', /** * Requesting this endpoint for a template like "twentytwentytwo//home" requires using @@ -82,20 +82,20 @@ public function register_routes() { */ 'sanitize_callback' => function( $id ) { $last_slash_pos = strrpos( $id, '/' ); - if ( $last_slash_pos === false ) { + if ( false === $last_slash_pos ) { return $id; } - $is_double_slashed = substr($id, $last_slash_pos - 1, 1 ) === '/'; - if ( ! $is_double_slashed ) { - $id = - substr( $id, 0, $last_slash_pos ) - . '/' - . substr( $id, $last_slash_pos ); + $is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/'; + if ( $is_double_slashed ) { + return $id; } - - return $id; - } + return ( + substr( $id, 0, $last_slash_pos ) + . '/' + . substr( $id, $last_slash_pos ) + ); + }, ), ), ), From 9d9c5406e60a8bd0167e547e7fe1d054839282fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 14:39:09 +0100 Subject: [PATCH 10/13] Test for both single and double slash --- ...s-gutenberg-rest-template-controller-test.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index 534cff4a11df57..2741d6ccb32662 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -162,10 +162,12 @@ public function test_get_item() { /** * Ticket 54507 + * + * @dataProvider template_endpoint_urls */ - public function test_get_item_works_with_a_single_slash() { + 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', '/wp/v2/templates/tt1-blocks/index' ); + $request = new WP_REST_Request( 'GET', $endpoint_url ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); @@ -192,6 +194,16 @@ public function test_get_item_works_with_a_single_slash() { ); } + /** + * + */ + public function template_endpoint_urls() { + return array( + array( '/wp/v2/templates/tt1-blocks/index' ), + array( '/wp/v2/templates/tt1-blocks//index' ), + ); + } + public function test_create_item() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/templates' ); From db0e18d42c570f43814f6a8415bfc6be34146ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 26 Nov 2021 14:49:37 +0100 Subject: [PATCH 11/13] Add unit tests for _sanitize_template_id --- ...ss-gutenberg-rest-templates-controller.php | 58 ++++++++++--------- ...utenberg-rest-template-controller-test.php | 26 +++++++++ 2 files changed, 57 insertions(+), 27 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 d1dab5eb9fa855..65343e8118bb68 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 @@ -69,33 +69,7 @@ public function register_routes() { 'id' => array( 'description' => __( 'The id of a template', 'gutenberg' ), 'type' => 'string', - - /** - * 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 - */ - 'sanitize_callback' => function( $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 ) - ); - }, + 'sanitize_callback' => array( $this, '_sanitize_template_id' ), ), ), ), @@ -143,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 2741d6ccb32662..da585722da6cc4 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -204,6 +204,32 @@ public function template_endpoint_urls() { ); } + /** + * 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' ); From 268b9addca2fc51fc14527258a1510c7b05aec5d Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 26 Nov 2021 14:53:48 +0100 Subject: [PATCH 12/13] Update phpunit/class-gutenberg-rest-template-controller-test.php --- phpunit/class-gutenberg-rest-template-controller-test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index da585722da6cc4..5cd483f643681b 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -163,7 +163,7 @@ public function test_get_item() { /** * Ticket 54507 * - * @dataProvider template_endpoint_urls + * @dataProvider get_template_endpoint_urls */ public function test_get_item_works_with_a_single_slash( $endpoint_url ) { wp_set_current_user( self::$admin_id ); From e65c390a5a143cfca879877ff4251b2ad214aa1a Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 26 Nov 2021 14:53:53 +0100 Subject: [PATCH 13/13] Update phpunit/class-gutenberg-rest-template-controller-test.php --- phpunit/class-gutenberg-rest-template-controller-test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/class-gutenberg-rest-template-controller-test.php b/phpunit/class-gutenberg-rest-template-controller-test.php index 5cd483f643681b..6787f4c0ab8fff 100644 --- a/phpunit/class-gutenberg-rest-template-controller-test.php +++ b/phpunit/class-gutenberg-rest-template-controller-test.php @@ -197,7 +197,7 @@ public function test_get_item_works_with_a_single_slash( $endpoint_url ) { /** * */ - public function template_endpoint_urls() { + public function get_template_endpoint_urls() { return array( array( '/wp/v2/templates/tt1-blocks/index' ), array( '/wp/v2/templates/tt1-blocks//index' ),