diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 93a2b8db0d..2a24a9048b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -574,7 +574,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { } if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { - $this->handle_template( $request['template'], $post_id ); + $this->handle_template( $request['template'], $post_id, true ); } $terms_update = $this->handle_terms( $post_id, $request ); @@ -1071,6 +1071,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { $prepared_post->ping_status = $request['ping_status']; } + if ( ! empty( $schema['properties']['template'] ) ) { + // Force template to null so that it can be handled exclusively by the REST controller. + $prepared_post->page_template = null; + } + /** * Filters a post before it is inserted via the REST API. * @@ -1147,20 +1152,60 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { } + /** + * Check whether the template is valid for the given post. + * + * @since 4.9.0 + * + * @param string $template Page template filename. + * @param WP_REST_Request $request Request. + * @return bool|WP_Error True if template is still valid or if the same as existing value, or false if template not supported. + */ + public function check_template( $template, $request ) { + + if ( ! $template ) { + return true; + } + + if ( $request['id'] ) { + $current_template = get_page_template_slug( $request['id'] ); + } else { + $current_template = ''; + } + + // Always allow for updating a post to the same template, even if that template is no longer supported. + if ( $template === $current_template ) { + return true; + } + + // If this is a create request, get_post() will return null and wp theme will fallback to the passed post type. + $allowed_templates = wp_get_theme()->get_page_templates( get_post( $request['id'] ), $this->post_type ); + + if ( isset( $allowed_templates[ $template ] ) ) { + return true; + } + + /* translators: 1: parameter, 2: list of valid values */ + return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), 'template', implode( ', ', array_keys( $allowed_templates ) ) ) ); + } + /** * Sets the template for a post. * * @since 4.7.0 + * @since 4.9.0 Introduced the $validate parameter. * * @param string $template Page template filename. * @param integer $post_id Post ID. + * @param bool $validate Whether to validate that the template selected is valid. */ - public function handle_template( $template, $post_id ) { - if ( in_array( $template, array_keys( wp_get_theme()->get_page_templates( get_post( $post_id ) ) ), true ) ) { - update_post_meta( $post_id, '_wp_page_template', $template ); - } else { - update_post_meta( $post_id, '_wp_page_template', '' ); + public function handle_template( $template, $post_id, $validate = false ) { + + if ( $validate && ! array_key_exists( $template, wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) { + $template = ''; } + + update_post_meta( $post_id, '_wp_page_template', $template ); } /** @@ -1996,8 +2041,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { $schema['properties']['template'] = array( 'description' => __( 'The theme file to use to display the object.' ), 'type' => 'string', - 'enum' => array_merge( array_keys( wp_get_theme()->get_page_templates( null, $this->post_type ) ), array( '' ) ), 'context' => array( 'view', 'edit' ), + 'arg_options' => array( + 'validate_callback' => array( $this, 'check_template' ), + ), ); $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index cf82fe5b0d..73d40d12c8 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -2667,6 +2667,33 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te $this->assertEquals( '', $post_template ); } + /** + * Test update_item() with same template that no longer exists. + * + * @covers WP_REST_Posts_Controller::check_template() + * @ticket 39996 + */ + public function test_update_item_with_same_template_that_no_longer_exists() { + + wp_set_current_user( self::$editor_id ); + + update_post_meta( self::$post_id, '_wp_page_template', 'post-my-invalid-template.php' ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $params = $this->set_post_data( array( + 'template' => 'post-my-invalid-template.php', + ) ); + $request->set_body_params( $params ); + $response = $this->server->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + + $data = $response->get_data(); + $post_template = get_page_template_slug( get_post( $data['id'] ) ); + + $this->assertEquals( 'post-my-invalid-template.php', $post_template ); + $this->assertEquals( 'post-my-invalid-template.php', $data['template'] ); + } public function verify_post_roundtrip( $input = array(), $expected_output = array() ) { // Create the post