Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REST] Restore the missing double slash in the ID received by /templates #36881

Merged
merged 13 commits into from
Nov 26, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ 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 );
$template = $this->get_template_by_id( $request['id'], true );
} else {
$template = gutenberg_get_block_template( $request['id'], $this->post_type );
$template = $this->get_template_by_id( $request['id'] );
}

if ( ! $template ) {
Expand All @@ -202,7 +202,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 ) );
}
Expand All @@ -223,7 +223,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;
Expand Down Expand Up @@ -292,7 +292,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 ) );
}
Expand Down Expand Up @@ -332,6 +332,61 @@ 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 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
*
* @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 ) {
// 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;
}
adamziel marked this conversation as resolved.
Show resolved Hide resolved
$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 );
adamziel marked this conversation as resolved.
Show resolved Hide resolved
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;
}

// Get the template.
$template = $getter( $inferred_id, $this->post_type );
}
return $template;
}

/**
* Prepares a single template for create or update.
*
Expand Down
38 changes: 38 additions & 0 deletions phpunit/class-gutenberg-rest-template-controller-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,44 @@ 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' );
$old_uri = $_SERVER['REQUEST_URI'];

$_SERVER['REQUEST_URI'] = '/index.php/wp-json/wp/v2/templates/tt1-blocks//index/?context=edit&_locale=user';
adamziel marked this conversation as resolved.
Show resolved Hide resolved

$response = rest_get_server()->dispatch( $request );

$_SERVER['REQUEST_URI'] = $old_uri;

$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' );
Expand Down