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

Add author support to templates and template parts #1937

4 changes: 4 additions & 0 deletions src/wp-includes/block-template-utils.php
Expand Up @@ -503,19 +503,23 @@ function _build_block_template_result_from_post( $post ) {
$has_theme_file = wp_get_theme()->get_stylesheet() === $theme &&
null !== _get_block_template_file( $post->post_type, $post->post_name );

$origin = get_post_meta( $post->ID, 'origin', true );

$template = new WP_Block_Template();
$template->wp_id = $post->ID;
$template->id = $theme . '//' . $post->post_name;
$template->theme = $theme;
$template->content = $post->post_content;
$template->slug = $post->post_name;
$template->source = 'custom';
$template->origin = ! empty( $origin ) ? $origin : null;
$template->type = $post->post_type;
$template->description = $post->post_excerpt;
$template->title = $post->post_title;
$template->status = $post->post_status;
$template->has_theme_file = $has_theme_file;
$template->is_custom = true;
$template->author = $post->post_author;

if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
$template->is_custom = false;
Expand Down
20 changes: 20 additions & 0 deletions src/wp-includes/class-wp-block-template.php
Expand Up @@ -77,6 +77,16 @@ class WP_Block_Template {
*/
public $source = 'theme';

/**
* Origin of the content when the content has been customized.
* When customized, origin takes on the value of source and source becomes
* 'custom'.
*
* @since 5.9.0
* @var string
*/
public $origin;

/**
* Post Id.
*
Expand Down Expand Up @@ -109,4 +119,14 @@ class WP_Block_Template {
* @var bool
*/
public $is_custom = true;

/**
* Author.
talldan marked this conversation as resolved.
Show resolved Hide resolved
*
* A value of 0 means no author.
*
* @since 5.9.0
* @var int
*/
public $author;
}
2 changes: 2 additions & 0 deletions src/wp-includes/post.php
Expand Up @@ -384,6 +384,7 @@ function create_initial_post_types() {
'excerpt',
'editor',
'revisions',
'author',
),
)
);
Expand Down Expand Up @@ -442,6 +443,7 @@ function create_initial_post_types() {
'excerpt',
'editor',
'revisions',
'author',
),
)
);
Expand Down
Expand Up @@ -244,6 +244,10 @@ public function update_item( $request ) {

$changes = $this->prepare_item_for_database( $request );

if ( is_wp_error( $changes ) ) {
return $changes;
}

if ( 'custom' === $template->source ) {
$result = wp_update_post( wp_slash( (array) $changes ), true );
} else {
Expand Down Expand Up @@ -294,6 +298,11 @@ public function create_item_permissions_check( $request ) {
*/
public function create_item( $request ) {
$prepared_post = $this->prepare_item_for_database( $request );

if ( is_wp_error( $prepared_post ) ) {
return $prepared_post;
}

$prepared_post->post_name = $request['slug'];
$post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true );
if ( is_wp_error( $post_id ) ) {
Expand Down Expand Up @@ -422,6 +431,9 @@ protected function prepare_item_for_database( $request ) {
$changes->tax_input = array(
'wp_theme' => $template->theme,
);
$changes->meta_input = array(
'origin' => $template->source,
);
} else {
$changes->post_name = $template->slug;
$changes->ID = $template->wp_id;
Expand Down Expand Up @@ -461,6 +473,24 @@ protected function prepare_item_for_database( $request ) {
}
}

if ( ! empty( $request['author'] ) ) {
$post_author = (int) $request['author'];

if ( get_current_user_id() !== $post_author ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this check for? Because wp_insert_post() will set post_author to get_current_user_id() if we don't specify?

Copy link
Author

@talldan talldan Nov 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I lifted it directly from the rest posts controller. 😄

My understanding is that it's a quick way to check if the provided author value is valid. If the id matches the current user it bypasses the more expensive check a few lines below. Seems to be based on the assumption that the current user is valid, which I think is fair enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine as it is a copy and paste from the post controller.

$user_obj = get_userdata( $post_author );

if ( ! $user_obj ) {
return new WP_Error(
'rest_invalid_author',
__( 'Invalid author ID.' ),
array( 'status' => 400 )
);
}
}

$changes->post_author = $post_author;
}

return $changes;
}

Expand Down Expand Up @@ -510,6 +540,10 @@ public function prepare_item_for_response( $item, $request ) { // phpcs:ignore V
$data['source'] = $template->source;
}

if ( rest_is_field_included( 'origin', $fields ) ) {
$data['origin'] = $template->origin;
}

if ( rest_is_field_included( 'type', $fields ) ) {
$data['type'] = $template->type;
}
Expand Down Expand Up @@ -547,6 +581,10 @@ public function prepare_item_for_response( $item, $request ) { // phpcs:ignore V
$data['has_theme_file'] = (bool) $template->has_theme_file;
}

if ( rest_is_field_included( 'author', $fields ) ) {
$data['author'] = (int) $template->author;
}

if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
$data['area'] = $template->area;
}
Expand Down Expand Up @@ -695,6 +733,12 @@ public function get_item_schema() {
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'origin' => array(
'description' => __( 'Source of a customized template' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'content' => array(
'description' => __( 'Content of template.' ),
'type' => array( 'object', 'string' ),
Expand Down Expand Up @@ -758,6 +802,11 @@ public function get_item_schema() {
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'author' => array(
'description' => __( 'The ID for the author of the template.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
),
);

Expand Down
31 changes: 29 additions & 2 deletions tests/phpunit/tests/rest-api/wpRestTemplatesController.php
Expand Up @@ -53,7 +53,6 @@ public static function wpTearDownAfterClass() {
wp_delete_post( self::$post->ID );
}


public function test_register_routes() {
$routes = rest_get_server()->get_routes();
$this->assertArrayHasKey( '/wp/v2/templates', $routes );
Expand Down Expand Up @@ -93,6 +92,7 @@ public function test_get_items() {
'theme' => 'default',
'slug' => 'my_template',
'source' => 'custom',
'origin' => null,
'type' => 'wp_template',
'description' => 'Description of my template.',
'title' => array(
Expand All @@ -102,6 +102,7 @@ public function test_get_items() {
'status' => 'publish',
'wp_id' => self::$post->ID,
'has_theme_file' => false,
'author' => 0,
),
$this->find_and_normalize_template_by_id( $data, 'default//my_template' )
);
Expand Down Expand Up @@ -134,6 +135,7 @@ public function test_get_item() {
'theme' => 'default',
'slug' => 'my_template',
'source' => 'custom',
'origin' => null,
'type' => 'wp_template',
'description' => 'Description of my template.',
'title' => array(
Expand All @@ -143,6 +145,7 @@ public function test_get_item() {
'status' => 'publish',
'wp_id' => self::$post->ID,
'has_theme_file' => false,
'author' => 0,
),
$data
);
Expand All @@ -161,6 +164,7 @@ public function test_create_item() {
'description' => 'Just a description',
'title' => 'My Template',
'content' => 'Content',
'author' => self::$admin_id,
)
);
$response = rest_get_server()->dispatch( $request );
Expand All @@ -177,6 +181,7 @@ public function test_create_item() {
),
'slug' => 'my_custom_template',
'source' => 'custom',
'origin' => null,
'type' => 'wp_template',
'description' => 'Just a description',
'title' => array(
Expand All @@ -185,6 +190,7 @@ public function test_create_item() {
),
'status' => 'publish',
'has_theme_file' => false,
'author' => self::$admin_id,
),
$data
);
Expand All @@ -207,6 +213,7 @@ public function test_create_item_raw() {
'content' => array(
'raw' => 'Content',
),
'author' => self::$admin_id,
)
);
$response = rest_get_server()->dispatch( $request );
Expand All @@ -223,6 +230,7 @@ public function test_create_item_raw() {
),
'slug' => 'my_custom_template_raw',
'source' => 'custom',
'origin' => null,
'type' => 'wp_template',
'description' => 'Just a description',
'title' => array(
Expand All @@ -231,11 +239,28 @@ public function test_create_item_raw() {
),
'status' => 'publish',
'has_theme_file' => false,
'author' => self::$admin_id,
),
$data
);
}

public function test_create_item_invalid_author() {
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/templates' );
$request->set_body_params(
array(
'slug' => 'my_custom_template_invalid_author',
'description' => 'Just a description',
'title' => 'My Template',
'content' => 'Content',
'author' => -1,
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_author', $response, 400 );
}

/**
* @covers WP_REST_Templates_Controller::update_item
*/
Expand Down Expand Up @@ -370,19 +395,21 @@ public function test_get_item_schema() {
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertCount( 11, $properties );
$this->assertCount( 13, $properties );
$this->assertArrayHasKey( 'id', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'theme', $properties );
$this->assertArrayHasKey( 'type', $properties );
$this->assertArrayHasKey( 'source', $properties );
$this->assertArrayHasKey( 'origin', $properties );
$this->assertArrayHasKey( 'content', $properties );
$this->assertArrayHasKey( 'title', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'status', $properties );
$this->assertArrayHasKey( 'wp_id', $properties );
$this->assertArrayHasKey( 'has_theme_file', $properties );
$this->assertArrayHasKey( 'author', $properties );
}

protected function find_and_normalize_template_by_id( $templates, $id ) {
Expand Down
30 changes: 30 additions & 0 deletions tests/qunit/fixtures/wp-api-generated.js
Expand Up @@ -5117,6 +5117,11 @@ mockedApiResponse.Schema = {
"private"
],
"required": false
},
"author": {
"description": "The ID for the author of the template.",
"type": "integer",
"required": false
}
}
}
Expand Down Expand Up @@ -5262,6 +5267,11 @@ mockedApiResponse.Schema = {
"private"
],
"required": false
},
"author": {
"description": "The ID for the author of the template.",
"type": "integer",
"required": false
}
}
},
Expand Down Expand Up @@ -5571,6 +5581,11 @@ mockedApiResponse.Schema = {
"private"
],
"required": false
},
"author": {
"description": "The ID for the author of the template.",
"type": "integer",
"required": false
}
}
}
Expand Down Expand Up @@ -5750,6 +5765,11 @@ mockedApiResponse.Schema = {
],
"required": false
},
"author": {
"description": "The ID for the author of the template.",
"type": "integer",
"required": false
},
"area": {
"description": "Where the template part is intended for use (header, footer, etc.)",
"type": "string",
Expand Down Expand Up @@ -5900,6 +5920,11 @@ mockedApiResponse.Schema = {
],
"required": false
},
"author": {
"description": "The ID for the author of the template.",
"type": "integer",
"required": false
},
"area": {
"description": "Where the template part is intended for use (header, footer, etc.)",
"type": "string",
Expand Down Expand Up @@ -6214,6 +6239,11 @@ mockedApiResponse.Schema = {
],
"required": false
},
"author": {
"description": "The ID for the author of the template.",
"type": "integer",
"required": false
},
"area": {
"description": "Where the template part is intended for use (header, footer, etc.)",
"type": "string",
Expand Down