Skip to content

Commit

Permalink
Templates: allow templates to be activated and deactivated
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix committed Dec 30, 2024
1 parent 37a06bd commit 2b0264c
Show file tree
Hide file tree
Showing 58 changed files with 1,004 additions and 558 deletions.
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 );
}
}
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;
}
}
5 changes: 3 additions & 2 deletions lib/compat/wordpress-6.8/preload.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) {
'url',
)
);
$paths[] = '/wp/v2/templates/lookup?slug=front-page';
$paths[] = '/wp/v2/templates/lookup?slug=home';
// There's an issue where preloaded data does not invalidate.
// $paths[] = '/wp/v2/templates/lookup?slug=front-page';
// $paths[] = '/wp/v2/templates/lookup?slug=home';
}

// Preload theme and global styles paths.
Expand Down
130 changes: 130 additions & 0 deletions lib/compat/wordpress-6.8/template-activate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?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;
}
3 changes: 3 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.7/rest-api.php';

// WordPress 6.8 compat.
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-static-templates-controller.php';
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-templates-controller.php';
require __DIR__ . '/compat/wordpress-6.8/template-activate.php';
require __DIR__ . '/compat/wordpress-6.8/block-comments.php';
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php';
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-post-types-controller-6-8.php';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const icons = {
post,
page,
wp_template: layout,
_wp_static_template: layout,
wp_template_part: symbolFilled,
};

Expand Down Expand Up @@ -169,7 +170,7 @@ const getNavigationCommandLoaderPerTemplate = ( templateType ) =>
return {
isBlockBasedTheme:
select( coreStore ).getCurrentTheme()?.is_block_theme,
canCreateTemplate: select( coreStore ).canUser( 'create', {
canCreateTemplate: select( coreStore ).canUser( 'read', {
kind: 'postType',
name: templateType,
} ),
Expand Down Expand Up @@ -420,6 +421,10 @@ export function useSiteEditorNavigationCommands() {
name: 'core/edit-site/navigate-templates',
hook: getNavigationCommandLoaderPerTemplate( 'wp_template' ),
} );
useCommandLoader( {
name: 'core/edit-site/navigate-templates',
hook: getNavigationCommandLoaderPerTemplate( '_wp_static_template' ),
} );
useCommandLoader( {
name: 'core/edit-site/navigate-template-parts',
hook: getNavigationCommandLoaderPerTemplate( 'wp_template_part' ),
Expand Down
44 changes: 43 additions & 1 deletion packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ export function receiveEntityRecords(
edits,
meta
) {
// If we receive an auto-draft template, pretend it's already published.
if ( kind === 'postType' && name === 'wp_template' ) {
records = ( Array.isArray( records ) ? records : [ records ] ).map(
( record ) =>
record.status === 'auto-draft'
? { ...record, status: 'publish' }
: record
);
}

// Auto drafts should not have titles, but some plugins rely on them so we can't filter this
// on the server.
if ( kind === 'postType' ) {
Expand Down Expand Up @@ -362,7 +372,7 @@ export const deleteEntityRecord =
*/
export const editEntityRecord =
( kind, name, recordId, edits, options = {} ) =>
( { select, dispatch } ) => {
async ( { select, dispatch, resolveSelect } ) => {
const entityConfig = select.getEntityConfig( kind, name );
if ( ! entityConfig ) {
throw new Error(
Expand Down Expand Up @@ -424,6 +434,33 @@ export const editEntityRecord =
],
options.isCached
);
// Temporary solution until we find the right UX: when the user
// modifies a template, we automatically set it active.
// It can be unchecked in multi-entity saving.
// This is to keep the current behaviour where templates are
// immediately active.
if (
! options.isCached &&
kind === 'postType' &&
name === 'wp_template'
) {
const site = await resolveSelect.getEntityRecord(
'root',
'site'
);
await dispatch.editEntityRecord(
'root',
'site',
undefined,
{
active_templates: {
...site.active_templates,
[ record.slug ]: record.id,
},
},
{ isCached: true }
);
}
}
dispatch( {
type: 'EDIT_ENTITY_RECORD',
Expand Down Expand Up @@ -673,6 +710,11 @@ export const saveEntityRecord =
),
};
}
// Unless there is no persisted record, set the status to
// publish.
if ( name === 'wp_template' && persistedRecord ) {
edits.status = 'publish';
}
updatedRecord = await __unstableFetch( {
path,
method: recordId ? 'PUT' : 'POST',
Expand Down
Loading

0 comments on commit 2b0264c

Please sign in to comment.