-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Allow template duplication + concept of active templates #67125
Open
ellatrix
wants to merge
9
commits into
trunk
Choose a base branch
from
try/active-templates-2
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
2b0264c
Templates: allow templates to be activated and deactivated
ellatrix 458a82f
Migrate to active templates
ellatrix e2ccef2
Use Badge component, add rename
ellatrix 49e5e31
Fix click handler
ellatrix 70f63fa
Fix some e2e tests
ellatrix 1a3fc33
Fix preload check
ellatrix a70a904
All theme templates are active by default
ellatrix 79e96df
Add backport log entry
ellatrix 0cb1a2d
Fix perf test
ellatrix File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
https://github.com/WordPress/wordpress-develop/pull/8063 | ||
|
||
* https://github.com/WordPress/gutenberg/pull/67125 |
125 changes: 125 additions & 0 deletions
125
lib/compat/wordpress-6.8/class-gutenberg-rest-static-templates-controller.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?php | ||
|
||
class Gutenberg_REST_Static_Templates_Controller extends Gutenberg_REST_Templates_Controller_6_7 { | ||
public function register_routes() { | ||
// Lists all templates. | ||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base, | ||
array( | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_items' ), | ||
'permission_callback' => array( $this, 'get_items_permissions_check' ), | ||
'args' => $this->get_collection_params(), | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) | ||
); | ||
|
||
// Lists/updates a single template based on the given id. | ||
register_rest_route( | ||
$this->namespace, | ||
// The route. | ||
sprintf( | ||
'/%s/(?P<id>%s%s)', | ||
$this->rest_base, | ||
/* | ||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`. | ||
* Excludes invalid directory name characters: `/:<>*?"|`. | ||
*/ | ||
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', | ||
// Matches the template name. | ||
'[\/\w%-]+' | ||
), | ||
array( | ||
'args' => array( | ||
'id' => array( | ||
'description' => __( 'The id of a template' ), | ||
'type' => 'string', | ||
'sanitize_callback' => array( $this, '_sanitize_template_id' ), | ||
), | ||
), | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_item' ), | ||
'permission_callback' => array( $this, 'get_item_permissions_check' ), | ||
'args' => array( | ||
'context' => $this->get_context_param( array( 'default' => 'view' ) ), | ||
), | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) | ||
); | ||
} | ||
|
||
public function get_item_schema() { | ||
$schema = parent::get_item_schema(); | ||
$schema['properties']['is_custom'] = array( | ||
'description' => __( 'Whether a template is a custom template.' ), | ||
'type' => 'bool', | ||
'context' => array( 'embed', 'view', 'edit' ), | ||
'readonly' => true, | ||
); | ||
$schema['properties']['plugin'] = array( | ||
'type' => 'string', | ||
'description' => __( 'Plugin that registered the template.' ), | ||
'readonly' => true, | ||
'context' => array( 'view', 'edit', 'embed' ), | ||
); | ||
return $schema; | ||
} | ||
|
||
public function get_items( $request ) { | ||
$query = array(); | ||
if ( isset( $request['area'] ) ) { | ||
$query['area'] = $request['area']; | ||
} | ||
if ( isset( $request['post_type'] ) ) { | ||
$query['post_type'] = $request['post_type']; | ||
} | ||
$template_files = _get_block_templates_files( 'wp_template', $query ); | ||
$query_result = array(); | ||
foreach ( $template_files as $template_file ) { | ||
$query_result[] = _build_block_template_result_from_file( $template_file, 'wp_template' ); | ||
} | ||
|
||
// Add templates registered in the template registry. Filtering out the ones which have a theme file. | ||
$registered_templates = WP_Block_Templates_Registry::get_instance()->get_by_query( $query ); | ||
$matching_registered_templates = array_filter( | ||
$registered_templates, | ||
function ( $registered_template ) use ( $template_files ) { | ||
foreach ( $template_files as $template_file ) { | ||
if ( $template_file['slug'] === $registered_template->slug ) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
); | ||
|
||
$query_result = array_merge( $query_result, $matching_registered_templates ); | ||
|
||
$templates = array(); | ||
foreach ( $query_result as $template ) { | ||
$item = $this->prepare_item_for_response( $template, $request ); | ||
$item->data['type'] = '_wp_static_template'; | ||
$templates[] = $this->prepare_response_for_collection( $item ); | ||
} | ||
|
||
return rest_ensure_response( $templates ); | ||
} | ||
|
||
public function get_item( $request ) { | ||
$template = get_block_file_template( $request['id'], 'wp_template' ); | ||
|
||
if ( ! $template ) { | ||
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); | ||
} | ||
|
||
$item = $this->prepare_item_for_response( $template, $request ); | ||
// adjust the template type here instead | ||
$item->data['type'] = '_wp_static_template'; | ||
return rest_ensure_response( $item ); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
lib/compat/wordpress-6.8/class-gutenberg-rest-templates-controller.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
class Gutenberg_REST_Templates_Controller extends WP_REST_Posts_Controller { | ||
protected function handle_status_param( $status, $request ) { | ||
if ( 'auto-draft' === $status ) { | ||
return $status; | ||
} | ||
return parent::handle_status_param( $status, $request ); | ||
} | ||
protected function add_additional_fields_schema( $schema ) { | ||
$schema = parent::add_additional_fields_schema( $schema ); | ||
|
||
$schema['properties']['status']['enum'][] = 'auto-draft'; | ||
return $schema; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
<?php | ||
|
||
// How does this work? | ||
// 1. For wp_template, we remove the custom templates controller, so it becomes | ||
// a normal posts endpoint, modified slightly to allow auto-drafts. | ||
add_filter( 'register_post_type_args', 'gutenberg_modify_wp_template_post_type_args', 10, 2 ); | ||
function gutenberg_modify_wp_template_post_type_args( $args, $post_type ) { | ||
if ( 'wp_template' === $post_type ) { | ||
$args['rest_base'] = 'wp_template'; | ||
$args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller'; | ||
$args['autosave_rest_controller_class'] = null; | ||
$args['revisions_rest_controller_class'] = null; | ||
} | ||
return $args; | ||
} | ||
|
||
// 2. We maintain the routes for /templates and /templates/lookup. I think we'll | ||
// need to deprecate /templates eventually, but we'll still want to be able | ||
// to lookup the active template for a specific slug, and probably get a list | ||
// of all _active_ templates. For that we can keep /lookup. | ||
add_action( 'rest_api_init', 'gutenberg_maintain_templates_routes' ); | ||
function gutenberg_maintain_templates_routes() { | ||
// This should later be changed in core so we don't need initialise | ||
// WP_REST_Templates_Controller with a post type. | ||
global $wp_post_types; | ||
$wp_post_types['wp_template']->rest_base = 'templates'; | ||
$controller = new Gutenberg_REST_Templates_Controller_6_7( 'wp_template' ); | ||
$wp_post_types['wp_template']->rest_base = 'wp_template'; | ||
$controller->register_routes(); | ||
} | ||
|
||
// 3. We need a route to get that raw static templates from themes and plugins. | ||
// I registered this as a post type route because right now the | ||
// EditorProvider assumes templates are posts. | ||
add_action( 'init', 'gutenberg_setup_static_template' ); | ||
function gutenberg_setup_static_template() { | ||
global $wp_post_types; | ||
$wp_post_types['_wp_static_template'] = clone $wp_post_types['wp_template']; | ||
$wp_post_types['_wp_static_template']->name = '_wp_static_template'; | ||
$wp_post_types['_wp_static_template']->rest_base = '_wp_static_template'; | ||
$wp_post_types['_wp_static_template']->rest_controller_class = 'Gutenberg_REST_Static_Templates_Controller'; | ||
|
||
register_setting( | ||
'reading', | ||
'active_templates', | ||
array( | ||
'type' => 'object', | ||
'show_in_rest' => array( | ||
'schema' => array( | ||
'type' => 'object', | ||
'additionalProperties' => true, | ||
), | ||
), | ||
'default' => array(), | ||
'label' => 'Active Templates', | ||
) | ||
); | ||
} | ||
|
||
add_filter( 'pre_wp_unique_post_slug', 'gutenberg_allow_template_slugs_to_be_duplicated', 10, 5 ); | ||
function gutenberg_allow_template_slugs_to_be_duplicated( $override, $slug, $post_id, $post_status, $post_type ) { | ||
return 'wp_template' === $post_type ? $slug : $override; | ||
} | ||
|
||
add_filter( 'pre_get_block_templates', 'gutenberg_pre_get_block_templates', 10, 3 ); | ||
function gutenberg_pre_get_block_templates( $output, $query, $template_type ) { | ||
if ( 'wp_template' === $template_type && ! empty( $query['slug__in'] ) ) { | ||
$active_templates = get_option( 'active_templates', array() ); | ||
$slugs = $query['slug__in']; | ||
$output = array(); | ||
foreach ( $slugs as $slug ) { | ||
if ( isset( $active_templates[ $slug ] ) ) { | ||
if ( false !== $active_templates[ $slug ] ) { | ||
$post = get_post( $active_templates[ $slug ] ); | ||
if ( $post && 'publish' === $post->post_status ) { | ||
$output[] = _build_block_template_result_from_post( $post ); | ||
} | ||
} else { | ||
// Deactivated template, fall back to next slug. | ||
$output[] = array(); | ||
} | ||
} | ||
} | ||
if ( empty( $output ) ) { | ||
$output = null; | ||
} | ||
} | ||
return $output; | ||
} | ||
|
||
// Whenever templates are queried by slug, never return any user templates. | ||
// We are handling that in gutenberg_pre_get_block_templates. | ||
function gutenberg_remove_tax_query_for_templates( $query ) { | ||
if ( isset( $query->query['post_type'] ) && 'wp_template' === $query->query['post_type'] ) { | ||
// We don't have templates with this status, that's the point. We want | ||
// this query to not return any user templates. | ||
$query->set( 'post_status', array( 'pending' ) ); | ||
} | ||
} | ||
|
||
add_filter( 'pre_get_block_templates', 'gutenberg_tax_pre_get_block_templates', 10, 3 ); | ||
function gutenberg_tax_pre_get_block_templates( $output, $query, $template_type ) { | ||
// Do not remove the tax query when querying for a specific slug. | ||
if ( 'wp_template' === $template_type && ! empty( $query['slug__in'] ) ) { | ||
add_action( 'pre_get_posts', 'gutenberg_remove_tax_query_for_templates' ); | ||
} | ||
return $output; | ||
} | ||
|
||
add_filter( 'get_block_templates', 'gutenberg_tax_get_block_templates', 10, 3 ); | ||
function gutenberg_tax_get_block_templates( $output, $query, $template_type ) { | ||
if ( 'wp_template' === $template_type && ! empty( $query['slug__in'] ) ) { | ||
remove_action( 'pre_get_posts', 'gutenberg_remove_tax_query_for_templates' ); | ||
} | ||
return $output; | ||
} | ||
|
||
// We need to set the theme for the template when it's created. See: | ||
// https://github.com/WordPress/wordpress-develop/blob/b2c8d8d2c8754cab5286b06efb4c11e2b6aa92d5/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php#L571-L578 | ||
add_action( 'rest_pre_insert_wp_template', 'gutenberg_set_active_template_theme', 10, 2 ); | ||
function gutenberg_set_active_template_theme( $changes, $request ) { | ||
$template = $request['id'] ? get_block_template( $request['id'], 'wp_template' ) : null; | ||
if ( $template ) { | ||
return $changes; | ||
} | ||
$changes->tax_input = array( | ||
'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(), | ||
); | ||
return $changes; | ||
} | ||
|
||
// Migrate existing "edited" templates. By existing, it means that the template | ||
// is active. | ||
add_action( 'init', 'gutenberg_migrate_existing_templates' ); | ||
function gutenberg_migrate_existing_templates() { | ||
$active_templates = get_option( 'active_templates' ); | ||
|
||
if ( $active_templates ) { | ||
return; | ||
} | ||
|
||
// Query all templates in the database. See `get_block_templates`. | ||
$wp_query_args = array( | ||
'post_status' => 'publish', | ||
'post_type' => 'wp_template', | ||
'posts_per_page' => -1, | ||
'no_found_rows' => true, | ||
'lazy_load_term_meta' => false, | ||
'tax_query' => array( | ||
array( | ||
'taxonomy' => 'wp_theme', | ||
'field' => 'name', | ||
'terms' => get_stylesheet(), | ||
), | ||
), | ||
); | ||
|
||
$template_query = new WP_Query( $wp_query_args ); | ||
$active_templates = array(); | ||
|
||
foreach ( $template_query->posts as $post ) { | ||
$active_templates[ $post->post_name ] = $post->ID; | ||
} | ||
|
||
update_option( 'active_templates', $active_templates ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we change the perm? Seems natural to check for create in
canCreate...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? The commands are meant to navigate to templates, not create new templates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But yes this variable should be renamed 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that using the
create
capability check here is odd but necessary. Users who can read templates might not have access to the Site Editor.See #60317 (comment).
P.S. We should extract similar minor and unrelated fixes into separate PRs. There's already a lot going on here and that would make reviewing bit easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then this check should be called differently and we should use something else to check if the user has access to the site editor. This is not a good proxy for checking site editor access, it is more restrictive now. Also we should then use it as a condition to prevent all site editor commands from loading?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to generalize it. The Site Editor needs more granular capability checks, but those should be mapped out separately - maybe even incorporated into the router.
As I mentioned, I would remove and extract similar fixes into separate PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, here the change is necessary because you cannot
create
static templates through the endpoint, you can only create custom templates.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I guess we need another additional check, not sure what