From a4f1949ec4d8a4b5bef7c8b40e5de98242c86248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Mon, 3 Jun 2024 16:32:03 +0200 Subject: [PATCH 01/49] Add plugin template registration API --- lib/block-templates.php | 14 + lib/class-wp-block-templates-registry.php | 318 ++++++++++++++++++ ...utenberg-rest-templates-controller-6-7.php | 167 +++++++++ lib/compat/wordpress-6.7/compat.php | 77 +++++ lib/compat/wordpress-6.7/rest-api.php | 20 ++ lib/load.php | 4 + .../src/utils/is-template-removable.js | 6 +- .../src/utils/is-template-revertable.js | 3 +- packages/editor/src/store/private-actions.js | 2 +- .../src/store/utils/is-template-revertable.js | 3 +- 10 files changed, 609 insertions(+), 5 deletions(-) create mode 100644 lib/block-templates.php create mode 100644 lib/class-wp-block-templates-registry.php create mode 100644 lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php create mode 100644 lib/compat/wordpress-6.7/compat.php diff --git a/lib/block-templates.php b/lib/block-templates.php new file mode 100644 index 00000000000000..04f53e5aa5e736 --- /dev/null +++ b/lib/block-templates.php @@ -0,0 +1,14 @@ +register( $template_name, 'wp_template', $args ); +} + +function gutenberg_register_block_template_part( $template_name, $args = array() ) { + return WP_Block_Templates_Registry::get_instance()->register( $template_name, 'wp_template_part', $args ); +} diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php new file mode 100644 index 00000000000000..0b9982b7c67198 --- /dev/null +++ b/lib/class-wp-block-templates-registry.php @@ -0,0 +1,318 @@ + $instance` pairs. + * + * @since 6.7.0 + * @var array $registered_block_templates { + * @type WP_Block_Template[] $wp_template Registered block templates. + * @type WP_Block_Template[] $wp_template_part Registered block template parts. + * } + */ + private $registered_block_templates = array( + 'wp_template' => array(), + 'wp_template_part' => array(), + ); + + /** + * Container for the main instance of the class. + * + * @since 6.7.0 + * @var WP_Block_Templates_Registry|null + */ + private static $instance = null; + + /** + * Registers a block template. + * + * @since 6.7.0 + * + * @param string|WP_Block_Template $template_name Block template name including namespace, or alternatively + * a complete WP_Block_Template instance. In case a WP_Block_Template + * is provided, the $args parameter will be ignored. + * @param string $template_type Template type, either `wp_template` or `wp_template_part`. + * @param array $args Optional. Array of block template arguments. + * @return WP_Block_Template|false The registered block template on success, or false on failure. + */ + public function register( $template_name, $template_type, $args = array() ) { + + $template = null; + if ( $template_name instanceof WP_Block_Template ) { + $template = $template_name; + $template_name = $template->name; + } + + if ( ! is_string( $template_name ) ) { + _doing_it_wrong( + __METHOD__, + __( 'Block template names must be strings.', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + _doing_it_wrong( + __METHOD__, + __( 'Block templates need to be of `wp_template` or `wp_template_part` type.', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + if ( preg_match( '/[A-Z]+/', $template_name ) ) { + _doing_it_wrong( + __METHOD__, + __( 'Block template names must not contain uppercase characters.', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; + if ( ! preg_match( $name_matcher, $template_name ) ) { + _doing_it_wrong( + __METHOD__, + __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + if ( $this->is_registered( $template_type, $template_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Template name. */ + sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ), + '6.7.0' + ); + return false; + } + + if ( ! $template ) { + $theme_name = get_stylesheet(); + $slug = isset( $args['slug'] ) ? $args['slug'] : explode( '//', $template_name )[1]; + + $template = new WP_Block_Template(); + $template->id = $theme_name . '//' . $slug; + $template->theme = $theme_name; + $template->plugin = isset( $args['plugin'] ) ? $args['plugin'] : ''; + $template->author = null; + $template->content = isset( $args['content'] ) ? $args['content'] : ''; + $template->source = 'plugin'; + $template->slug = $slug; + $template->type = $template_type; + $template->title = isset( $args['title'] ) ? $args['title'] : ''; + $template->description = isset( $args['description'] ) ? $args['description'] : ''; + $template->status = 'publish'; + $template->has_theme_file = true; + $template->origin = 'plugin'; + $template->is_custom = true; + $template->post_types = 'wp_template' === $template_type && isset( $args['post_types'] ) ? $args['post_types'] : ''; + $template->area = 'wp_template_part' === $template_type && isset( $args['area'] ) ? $args['area'] : ''; + } + + $this->registered_block_templates[ $template_type ][ $template_name ] = $template; + + return $template; + } + + /** + * Retrieves all registered block templates by type. + * + * @since 6.7.0 + * + * @param string $template_type Template type, either `wp_template` or `wp_template_part`. + * @return WP_Block_Template[]|false Associative array of `$block_template_name => $block_template` pairs. + */ + public function get_all_registered( $template_type ) { + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + _doing_it_wrong( + __METHOD__, + __( 'Only valid block template types are `wp_template` and `wp_template_part`.', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + return $this->registered_block_templates[ $template_type ]; + } + + /** + * Retrieves a registered template by its type and name. + * + * @since 6.7.0 + * + * @param string $template_type Template type, either `wp_template` or `wp_template_part`. + * @param string $template_name Block template name including namespace. + * @return WP_Block_Template|null|false The registered block template, or null if it is not registered. + */ + public function get_registered( $template_type, $template_name ) { + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + _doing_it_wrong( + __METHOD__, + __( 'Only valid block template types are `wp_template` and `wp_template_part`.', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + if ( ! $this->is_registered( $template_type, $template_name ) ) { + return null; + } + + return $this->registered_block_templates[ $template_type ][ $template_name ]; + } + + /** + * Retrieves a registered template by its type and slug. + * + * @since 6.7.0 + * + * @param string $template_type Template type, either `wp_template` or `wp_template_part`. + * @param string $template_slug Slug of the template. + * @return WP_Block_Template|null The registered block template, or null if it is not registered. + */ + public function get_by_slug( $template_type, $template_slug ) { + $all_templates = $this->get_all_registered( $template_type ); + + if ( ! $all_templates ) { + return null; + } + + foreach ( $all_templates as $template ) { + if ( $template->slug === $template_slug ) { + return $template; + } + } + + return null; + } + + /** + * Retrieves registered block templates matching a query. + * + * @since 6.7.0 + * + * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. + * @param array $query { + * Arguments to retrieve templates. Optional, empty by default. + * + * @type string[] $slug__in List of slugs to include. + * @type string[] $slug__not_in List of slugs to skip. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only). + * @type string $post_type Post type to get the templates for. + * } + */ + public function get_by_query( $template_type, $query = array() ) { + $all_templates = $this->get_all_registered( $template_type ); + + if ( ! $all_templates ) { + return array(); + } + + $slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); + $slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array(); + $area = isset( $query['area'] ) ? $query['area'] : null; + $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; + + foreach ( $all_templates as $template_name => $template ) { + if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include, true ) ) { + unset( $all_templates[ $template_name ] ); + } + + if ( ! empty( $slugs_to_skip ) && in_array( $template->slug, $slugs_to_skip, true ) ) { + unset( $all_templates[ $template_name ] ); + } + + if ( 'wp_template_part' === $template_type && isset( $area ) && $template->area !== $area ) { + unset( $all_templates[ $template_name ] ); + } + + if ( 'wp_template' === $template_type && ! empty( $post_type ) && ! in_array( $post_type, $template->post_types, true ) ) { + unset( $all_templates[ $template_name ] ); + } + } + + return $all_templates; + } + + /** + * Checks if a block template is registered. + * + * @since 6.7.0 + * + * @param string $template_type Template type, either `wp_template` or `wp_template_part`. + * @param string $template_name Block template name including namespace. + * @return bool True if the template is registered, false otherwise. + */ + public function is_registered( $template_type, $template_name ) { + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + _doing_it_wrong( + __METHOD__, + __( 'Only valid block template types are `wp_template` and `wp_template_part`.', 'gutenberg' ), + '6.7.0' + ); + return false; + } + + return isset( $this->registered_block_templates[ $template_type ][ $template_name ] ); + } + + /** + * Unregisters a block template. + * + * @since 6.7.0 + * + * @param string $template_type Template type, either `wp_template` or `wp_template_part`. + * @param string $name Block template name including namespace. + * @return WP_Block_Template|false The unregistered block template on success, or false on failure. + */ + public function unregister( $template_type, $template_name ) { + if ( ! $this->is_registered( $template_type, $template_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block name. */ + sprintf( __( 'Block template "%s" is not registered.', 'gutenberg' ), $template_name ), + '6.7.0' + ); + return false; + } + + $unregistered_block_template = $this->registered_block_templates[ $template_type ][ $template_name ]; + unset( $this->registered_block_templates[ $template_type ][ $template_name ] ); + + return $unregistered_block_template; + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.7.0 + * + * @return WP_Block_Templates_Registry The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } + } +} diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php new file mode 100644 index 00000000000000..348ff86602411e --- /dev/null +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -0,0 +1,167 @@ +post_type ); + // @core-merge: Add a special case for plugin templates. + } elseif ( isset( $request['source'] ) && 'plugin' === $request['source'] ) { + list( , $slug ) = explode( '//', $request['id'] ); + $template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $this->post_type, $slug ); + // @core-merge: End of changes to merge in core. + } else { + $template = get_block_template( $request['id'], $this->post_type ); + } + + if ( ! $template ) { + return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); + } + + return $this->prepare_item_for_response( $template, $request ); + } + + /** + * Prepare a single template output for response + * + * @param WP_Block_Template $item Template instance. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $template = $item; + + $fields = $this->get_fields_for_response( $request ); + + if ( 'plugin' !== $item->origin ) { + return parent::prepare_item_for_response( $item, $request ); + } + // @core-merge: Fix wrong author in plugin templates. + $cloned_item = clone $item; + // Set the origin as theme when calling the previous `prepare_item_for_response()` to prevent warnings when generating the author text. + $cloned_item->origin = 'theme'; + $response = parent::prepare_item_for_response( $cloned_item, $request ); + $data = $response->data; + + if ( rest_is_field_included( 'origin', $fields ) ) { + $data['origin'] = 'plugin'; + } + + if ( rest_is_field_included( 'author_text', $fields ) ) { + $data['author_text'] = $this->get_wp_templates_author_text_field( $template ); + } + + if ( rest_is_field_included( 'original_source', $fields ) ) { + $data['original_source'] = $this->get_wp_templates_original_source_field( $template ); + } + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $template->id ); + $response->add_links( $links ); + if ( ! empty( $links['self']['href'] ) ) { + $actions = $this->get_available_actions(); + $self = $links['self']['href']; + foreach ( $actions as $rel ) { + $response->add_link( $rel, $self ); + } + } + } + + return $response; + } + + /** + * Returns the source from where the template originally comes from. + * + * @param WP_Block_Template $template_object Template instance. + * @return string Original source of the template one of theme, plugin, site, or user. + */ + private static function get_wp_templates_original_source_field( $template_object ) { + if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { + // Added by theme. + // Template originally provided by a theme, but customized by a user. + // Templates originally didn't have the 'origin' field so identify + // older customized templates by checking for no origin and a 'theme' + // or 'custom' source. + if ( $template_object->has_theme_file && + ( 'theme' === $template_object->origin || ( + empty( $template_object->origin ) && in_array( + $template_object->source, + array( + 'theme', + 'custom', + ), + true + ) ) + ) + ) { + return 'theme'; + } + + // Added by plugin. + if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) { + return 'plugin'; + } + + // Added by site. + // Template was created from scratch, but has no author. Author support + // was only added to templates in WordPress 5.9. Fallback to showing the + // site logo and title. + if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { + return 'site'; + } + } + + // Added by user. + return 'user'; + } + + /** + * Returns a human readable text for the author of the template. + * + * @param WP_Block_Template $template_object Template instance. + * @return string Human readable text for the author. + */ + private static function get_wp_templates_author_text_field( $template_object ) { + $original_source = self::get_wp_templates_original_source_field( $template_object ); + switch ( $original_source ) { + case 'theme': + $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); + return empty( $theme_name ) ? $template_object->theme : $theme_name; + case 'plugin': + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $plugins = get_plugins(); + // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates. + $plugin_name = isset( $template_object->plugin ) ? $template_object->plugin . '/' . $template_object->plugin : $template_object->theme; + $plugin = $plugins[ plugin_basename( sanitize_text_field( $plugin_name . '.php' ) ) ]; + return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; + // @core-merge: End of changes to merge in core. + case 'site': + return get_bloginfo( 'name' ); + case 'user': + $author = get_user_by( 'id', $template_object->author ); + if ( ! $author ) { + return __( 'Unknown author' ); + } + return $author->get( 'display_name' ); + } + } +} diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php new file mode 100644 index 00000000000000..9a6a22ed02bf48 --- /dev/null +++ b/lib/compat/wordpress-6.7/compat.php @@ -0,0 +1,77 @@ +get_by_query( $template_type, $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 ); + } + + return $query_result; +} +add_filter( 'get_block_templates', '_gutenberg_add_block_templates_from_registry', 10, 3 ); + +/** + * Hooks into `get_block_file_template` so templates from the registry are also returned. + * + * @param WP_Block_Template|null $block_template The found block template, or null if there is none. + * @param string $id Template unique identifier (example: 'theme_slug//template_slug'). + * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. + * @return WP_Block_Template|null The block template that was already found or from the registry. In case the template was already found, add the necessary details from the registry. + */ +function _gutenberg_add_block_file_templates_from_registry( $block_template, $id, $template_type ) { + if ( $block_template ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_type, $block_template->slug ); + if ( $registered_template ) { + $block_template->plugin = $registered_template->plugin; + } + return $block_template; + } + + $parts = explode( '//', $id, 2 ); + + if ( count( $parts ) < 2 ) { + return $block_template; + } + + list( , $slug ) = $parts; + return WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_type, $slug ); +} +add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 3 ); diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 713d31c4632c74..6a98bcce5f04ca 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -29,3 +29,23 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) { return $paths; } add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 ); + +if ( ! function_exists( 'wp_api_template_registry' ) ) { + /** + * Hook in to the template and template part post types and modify the + * the rest endpoint to include modifications to read templates from the + * BlockTemplatesRegistry. + * + * @param array $args Current registered post type args. + * @param string $post_type Name of post type. + * + * @return array + */ + function wp_api_template_registry( $args, $post_type ) { + if ( 'wp_template' === $post_type || 'wp_template_part' === $post_type ) { + $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_7'; + } + return $args; + } +} +add_filter( 'register_post_type_args', 'wp_api_template_registry', 10, 2 ); diff --git a/lib/load.php b/lib/load.php index c5f12af1654df2..54563d9e1cc900 100644 --- a/lib/load.php +++ b/lib/load.php @@ -41,6 +41,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; // WordPress 6.7 compat. + require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/rest-api.php'; // Plugin specific code. @@ -104,6 +105,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.7/blocks.php'; require __DIR__ . '/compat/wordpress-6.7/block-bindings.php'; require __DIR__ . '/compat/wordpress-6.7/script-modules.php'; +require __DIR__ . '/compat/wordpress-6.7/compat.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; @@ -142,7 +144,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/class-wp-theme-json-resolver-gutenberg.php'; require __DIR__ . '/class-wp-theme-json-schema-gutenberg.php'; require __DIR__ . '/class-wp-duotone-gutenberg.php'; +require __DIR__ . '/class-wp-block-templates-registry.php'; require __DIR__ . '/blocks.php'; +require __DIR__ . '/block-templates.php'; require __DIR__ . '/block-editor-settings.php'; require __DIR__ . '/client-assets.php'; require __DIR__ . '/demo.php'; diff --git a/packages/edit-site/src/utils/is-template-removable.js b/packages/edit-site/src/utils/is-template-removable.js index 9cb1de23daab75..6566c6e4c1109b 100644 --- a/packages/edit-site/src/utils/is-template-removable.js +++ b/packages/edit-site/src/utils/is-template-removable.js @@ -7,7 +7,7 @@ import { TEMPLATE_ORIGINS } from './constants'; * Check if a template is removable. * * @param {Object} template The template entity to check. - * @return {boolean} Whether the template is revertable. + * @return {boolean} Whether the template is removable. */ export default function isTemplateRemovable( template ) { if ( ! template ) { @@ -15,6 +15,8 @@ export default function isTemplateRemovable( template ) { } return ( - template.source === TEMPLATE_ORIGINS.custom && ! template.has_theme_file + template.source === TEMPLATE_ORIGINS.custom && + template.origin !== 'plugin' && + ! template.has_theme_file ); } diff --git a/packages/edit-site/src/utils/is-template-revertable.js b/packages/edit-site/src/utils/is-template-revertable.js index a6274d07ebebb6..e9b65df0580c41 100644 --- a/packages/edit-site/src/utils/is-template-revertable.js +++ b/packages/edit-site/src/utils/is-template-revertable.js @@ -15,7 +15,8 @@ export default function isTemplateRevertable( template ) { } /* eslint-disable camelcase */ return ( - template?.source === TEMPLATE_ORIGINS.custom && template?.has_theme_file + template?.source === TEMPLATE_ORIGINS.custom && + ( template?.origin === 'plugin' || template?.has_theme_file ) ); /* eslint-enable camelcase */ } diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js index 0996d6eb8b9d32..e22929011256d5 100644 --- a/packages/editor/src/store/private-actions.js +++ b/packages/editor/src/store/private-actions.js @@ -269,7 +269,7 @@ export const revertTemplate = const fileTemplatePath = addQueryArgs( `${ templateEntityConfig.baseURL }/${ template.id }`, - { context: 'edit', source: 'theme' } + { context: 'edit', source: template.origin } ); const fileTemplate = await apiFetch( { path: fileTemplatePath } ); diff --git a/packages/editor/src/store/utils/is-template-revertable.js b/packages/editor/src/store/utils/is-template-revertable.js index a09715af875bc2..e311667aa9a776 100644 --- a/packages/editor/src/store/utils/is-template-revertable.js +++ b/packages/editor/src/store/utils/is-template-revertable.js @@ -18,6 +18,7 @@ export default function isTemplateRevertable( templateOrTemplatePart ) { return ( templateOrTemplatePart.source === TEMPLATE_ORIGINS.custom && - templateOrTemplatePart.has_theme_file + ( templateOrTemplatePart?.origin === 'plugin' || + templateOrTemplatePart?.has_theme_file ) ); } From b9d439b8f998d82e33fb05bdffdcdd99744b8325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 26 Jun 2024 16:37:35 +0200 Subject: [PATCH 02/49] Remove support for template parts in the registry --- lib/block-templates.php | 6 +- lib/class-wp-block-templates-registry.php | 110 +++++------------- ...utenberg-rest-templates-controller-6-7.php | 4 +- lib/compat/wordpress-6.7/compat.php | 11 +- lib/compat/wordpress-6.7/rest-api.php | 4 +- 5 files changed, 37 insertions(+), 98 deletions(-) diff --git a/lib/block-templates.php b/lib/block-templates.php index 04f53e5aa5e736..f9a7e8eb1c9999 100644 --- a/lib/block-templates.php +++ b/lib/block-templates.php @@ -6,9 +6,5 @@ */ function gutenberg_register_block_template( $template_name, $args = array() ) { - return WP_Block_Templates_Registry::get_instance()->register( $template_name, 'wp_template', $args ); -} - -function gutenberg_register_block_template_part( $template_name, $args = array() ) { - return WP_Block_Templates_Registry::get_instance()->register( $template_name, 'wp_template_part', $args ); + return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args ); } diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php index 0b9982b7c67198..beab5bc196d751 100644 --- a/lib/class-wp-block-templates-registry.php +++ b/lib/class-wp-block-templates-registry.php @@ -8,7 +8,7 @@ if ( ! class_exists( 'WP_Block_Templates_Registry' ) ) { /** - * Core class used for interacting with block templates and block template parts. + * Core class used for interacting with block templates. * * @since 6.7.0 */ @@ -17,15 +17,9 @@ final class WP_Block_Templates_Registry { * Registered block templates, as `$name => $instance` pairs. * * @since 6.7.0 - * @var array $registered_block_templates { - * @type WP_Block_Template[] $wp_template Registered block templates. - * @type WP_Block_Template[] $wp_template_part Registered block template parts. - * } + * @var WP_Block_Template[] $registered_block_templates Registered block templates. */ - private $registered_block_templates = array( - 'wp_template' => array(), - 'wp_template_part' => array(), - ); + private $registered_block_templates = array(); /** * Container for the main instance of the class. @@ -43,11 +37,10 @@ final class WP_Block_Templates_Registry { * @param string|WP_Block_Template $template_name Block template name including namespace, or alternatively * a complete WP_Block_Template instance. In case a WP_Block_Template * is provided, the $args parameter will be ignored. - * @param string $template_type Template type, either `wp_template` or `wp_template_part`. * @param array $args Optional. Array of block template arguments. * @return WP_Block_Template|false The registered block template on success, or false on failure. */ - public function register( $template_name, $template_type, $args = array() ) { + public function register( $template_name, $args = array() ) { $template = null; if ( $template_name instanceof WP_Block_Template ) { @@ -64,15 +57,6 @@ public function register( $template_name, $template_type, $args = array() ) { return false; } - if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { - _doing_it_wrong( - __METHOD__, - __( 'Block templates need to be of `wp_template` or `wp_template_part` type.', 'gutenberg' ), - '6.7.0' - ); - return false; - } - if ( preg_match( '/[A-Z]+/', $template_name ) ) { _doing_it_wrong( __METHOD__, @@ -92,7 +76,7 @@ public function register( $template_name, $template_type, $args = array() ) { return false; } - if ( $this->is_registered( $template_type, $template_name ) ) { + if ( $this->is_registered( $template_name ) ) { _doing_it_wrong( __METHOD__, /* translators: %s: Template name. */ @@ -114,80 +98,58 @@ public function register( $template_name, $template_type, $args = array() ) { $template->content = isset( $args['content'] ) ? $args['content'] : ''; $template->source = 'plugin'; $template->slug = $slug; - $template->type = $template_type; + $template->type = 'wp_template'; $template->title = isset( $args['title'] ) ? $args['title'] : ''; $template->description = isset( $args['description'] ) ? $args['description'] : ''; $template->status = 'publish'; $template->has_theme_file = true; $template->origin = 'plugin'; $template->is_custom = true; - $template->post_types = 'wp_template' === $template_type && isset( $args['post_types'] ) ? $args['post_types'] : ''; - $template->area = 'wp_template_part' === $template_type && isset( $args['area'] ) ? $args['area'] : ''; + $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; } - $this->registered_block_templates[ $template_type ][ $template_name ] = $template; + $this->registered_block_templates[ $template_name ] = $template; return $template; } /** - * Retrieves all registered block templates by type. + * Retrieves all registered block templates. * * @since 6.7.0 * - * @param string $template_type Template type, either `wp_template` or `wp_template_part`. * @return WP_Block_Template[]|false Associative array of `$block_template_name => $block_template` pairs. */ - public function get_all_registered( $template_type ) { - if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { - _doing_it_wrong( - __METHOD__, - __( 'Only valid block template types are `wp_template` and `wp_template_part`.', 'gutenberg' ), - '6.7.0' - ); - return false; - } - - return $this->registered_block_templates[ $template_type ]; + public function get_all_registered() { + return $this->registered_block_templates; } /** - * Retrieves a registered template by its type and name. + * Retrieves a registered template by its and name. * * @since 6.7.0 * - * @param string $template_type Template type, either `wp_template` or `wp_template_part`. * @param string $template_name Block template name including namespace. * @return WP_Block_Template|null|false The registered block template, or null if it is not registered. */ - public function get_registered( $template_type, $template_name ) { - if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { - _doing_it_wrong( - __METHOD__, - __( 'Only valid block template types are `wp_template` and `wp_template_part`.', 'gutenberg' ), - '6.7.0' - ); - return false; - } - - if ( ! $this->is_registered( $template_type, $template_name ) ) { + public function get_registered( $template_name ) { + if ( ! $this->is_registered( $template_name ) ) { return null; } - return $this->registered_block_templates[ $template_type ][ $template_name ]; + return $this->registered_block_templates[ $template_name ]; } /** - * Retrieves a registered template by its type and slug. + * Retrieves a registered template by its slug. * * @since 6.7.0 * - * @param string $template_type Template type, either `wp_template` or `wp_template_part`. * @param string $template_slug Slug of the template. * @return WP_Block_Template|null The registered block template, or null if it is not registered. */ - public function get_by_slug( $template_type, $template_slug ) { - $all_templates = $this->get_all_registered( $template_type ); + public function get_by_slug( $template_slug ) { + $all_templates = $this->get_all_registered(); if ( ! $all_templates ) { return null; @@ -207,18 +169,16 @@ public function get_by_slug( $template_type, $template_slug ) { * * @since 6.7.0 * - * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. * @param array $query { * Arguments to retrieve templates. Optional, empty by default. * * @type string[] $slug__in List of slugs to include. * @type string[] $slug__not_in List of slugs to skip. - * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only). * @type string $post_type Post type to get the templates for. * } */ - public function get_by_query( $template_type, $query = array() ) { - $all_templates = $this->get_all_registered( $template_type ); + public function get_by_query( $query = array() ) { + $all_templates = $this->get_all_registered(); if ( ! $all_templates ) { return array(); @@ -226,7 +186,6 @@ public function get_by_query( $template_type, $query = array() ) { $slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); $slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array(); - $area = isset( $query['area'] ) ? $query['area'] : null; $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; foreach ( $all_templates as $template_name => $template ) { @@ -238,11 +197,7 @@ public function get_by_query( $template_type, $query = array() ) { unset( $all_templates[ $template_name ] ); } - if ( 'wp_template_part' === $template_type && isset( $area ) && $template->area !== $area ) { - unset( $all_templates[ $template_name ] ); - } - - if ( 'wp_template' === $template_type && ! empty( $post_type ) && ! in_array( $post_type, $template->post_types, true ) ) { + if ( ! empty( $post_type ) && ! in_array( $post_type, $template->post_types, true ) ) { unset( $all_templates[ $template_name ] ); } } @@ -255,21 +210,11 @@ public function get_by_query( $template_type, $query = array() ) { * * @since 6.7.0 * - * @param string $template_type Template type, either `wp_template` or `wp_template_part`. * @param string $template_name Block template name including namespace. * @return bool True if the template is registered, false otherwise. */ - public function is_registered( $template_type, $template_name ) { - if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { - _doing_it_wrong( - __METHOD__, - __( 'Only valid block template types are `wp_template` and `wp_template_part`.', 'gutenberg' ), - '6.7.0' - ); - return false; - } - - return isset( $this->registered_block_templates[ $template_type ][ $template_name ] ); + public function is_registered( $template_name ) { + return isset( $this->registered_block_templates[ $template_name ] ); } /** @@ -277,12 +222,11 @@ public function is_registered( $template_type, $template_name ) { * * @since 6.7.0 * - * @param string $template_type Template type, either `wp_template` or `wp_template_part`. * @param string $name Block template name including namespace. * @return WP_Block_Template|false The unregistered block template on success, or false on failure. */ - public function unregister( $template_type, $template_name ) { - if ( ! $this->is_registered( $template_type, $template_name ) ) { + public function unregister( $template_name ) { + if ( ! $this->is_registered( $template_name ) ) { _doing_it_wrong( __METHOD__, /* translators: %s: Block name. */ @@ -292,8 +236,8 @@ public function unregister( $template_type, $template_name ) { return false; } - $unregistered_block_template = $this->registered_block_templates[ $template_type ][ $template_name ]; - unset( $this->registered_block_templates[ $template_type ][ $template_name ] ); + $unregistered_block_template = $this->registered_block_templates[ $template_name ]; + unset( $this->registered_block_templates[ $template_name ] ); return $unregistered_block_template; } diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 348ff86602411e..61794f13bb5d1b 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -22,7 +22,7 @@ public function get_item( $request ) { // @core-merge: Add a special case for plugin templates. } elseif ( isset( $request['source'] ) && 'plugin' === $request['source'] ) { list( , $slug ) = explode( '//', $request['id'] ); - $template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $this->post_type, $slug ); + $template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug ); // @core-merge: End of changes to merge in core. } else { $template = get_block_template( $request['id'], $this->post_type ); @@ -93,7 +93,7 @@ public function prepare_item_for_response( $item, $request ) { * @return string Original source of the template one of theme, plugin, site, or user. */ private static function get_wp_templates_original_source_field( $template_object ) { - if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { + if ( 'wp_template' === $template_object->type ) { // Added by theme. // Template originally provided by a theme, but customized by a user. // Templates originally didn't have the 'origin' field so identify diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index 9a6a22ed02bf48..4d8f8074a2bef8 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -29,7 +29,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t /* * 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( $template_type, $query ); + $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 ) { @@ -53,12 +53,11 @@ function ( $registered_template ) use ( $template_files ) { * * @param WP_Block_Template|null $block_template The found block template, or null if there is none. * @param string $id Template unique identifier (example: 'theme_slug//template_slug'). - * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'. * @return WP_Block_Template|null The block template that was already found or from the registry. In case the template was already found, add the necessary details from the registry. */ -function _gutenberg_add_block_file_templates_from_registry( $block_template, $id, $template_type ) { +function _gutenberg_add_block_file_templates_from_registry( $block_template, $id ) { if ( $block_template ) { - $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_type, $block_template->slug ); + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; } @@ -72,6 +71,6 @@ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id } list( , $slug ) = $parts; - return WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_type, $slug ); + return WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug ); } -add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 3 ); +add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 2 ); diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 6a98bcce5f04ca..657ab03e3acc03 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -32,8 +32,8 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) { if ( ! function_exists( 'wp_api_template_registry' ) ) { /** - * Hook in to the template and template part post types and modify the - * the rest endpoint to include modifications to read templates from the + * Hook in to the template and template part post types and modify the rest + * endpoint to include modifications to read templates from the * BlockTemplatesRegistry. * * @param array $args Current registered post type args. From 90669a8d8f023ff3754ed41fd39e2a17310f09a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 26 Jun 2024 17:21:24 +0200 Subject: [PATCH 03/49] Rename API from gutenberg_register_block_template() to gutenberg_register_template() --- lib/block-templates.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/block-templates.php b/lib/block-templates.php index f9a7e8eb1c9999..c866d4b21323ba 100644 --- a/lib/block-templates.php +++ b/lib/block-templates.php @@ -5,6 +5,6 @@ * @package gutenberg */ -function gutenberg_register_block_template( $template_name, $args = array() ) { +function gutenberg_register_template( $template_name, $args = array() ) { return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args ); } From e1612d645e4dc24abd35fec1f95bd45db98ed593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 26 Jun 2024 17:21:32 +0200 Subject: [PATCH 04/49] Add @core-merge comment specifying a function hasn't been modified --- .../class-gutenberg-rest-templates-controller-6-7.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 61794f13bb5d1b..b9d38d0a65f1e3 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -138,6 +138,7 @@ private static function get_wp_templates_original_source_field( $template_object * @param WP_Block_Template $template_object Template instance. * @return string Human readable text for the author. */ + // @core-merge: Nothing has changed in this function, the only reason to include it here is that it's a private function. private static function get_wp_templates_author_text_field( $template_object ) { $original_source = self::get_wp_templates_original_source_field( $template_object ); switch ( $original_source ) { From 8d56d846090c27ebc40239c95751490870c83521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 13:36:45 +0200 Subject: [PATCH 05/49] Update WP_Block_Templates_Registry so it returns an error when using the API incorrectly --- lib/class-wp-block-templates-registry.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php index beab5bc196d751..64994ff873506e 100644 --- a/lib/class-wp-block-templates-registry.php +++ b/lib/class-wp-block-templates-registry.php @@ -51,10 +51,10 @@ public function register( $template_name, $args = array() ) { if ( ! is_string( $template_name ) ) { _doing_it_wrong( __METHOD__, - __( 'Block template names must be strings.', 'gutenberg' ), + __( 'Block template names must be a string.', 'gutenberg' ), '6.7.0' ); - return false; + return new WP_Error( 'template_name_no_string', __( 'Block template names must be a string.' ) ); } if ( preg_match( '/[A-Z]+/', $template_name ) ) { @@ -63,7 +63,7 @@ public function register( $template_name, $args = array() ) { __( 'Block template names must not contain uppercase characters.', 'gutenberg' ), '6.7.0' ); - return false; + return new WP_Error( 'template_name_no_uppercase', __( 'Block template names must not contain uppercase characters.' ) ); } $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; @@ -73,7 +73,7 @@ public function register( $template_name, $args = array() ) { __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), '6.7.0' ); - return false; + return new WP_Error( 'template_no_prefix', __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template' ) ); } if ( $this->is_registered( $template_name ) ) { @@ -83,7 +83,7 @@ public function register( $template_name, $args = array() ) { sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ), '6.7.0' ); - return false; + return new WP_Error( 'template_already_registered', __( 'Template "%s" is already registered.' ) ); } if ( ! $template ) { @@ -229,11 +229,11 @@ public function unregister( $template_name ) { if ( ! $this->is_registered( $template_name ) ) { _doing_it_wrong( __METHOD__, - /* translators: %s: Block name. */ - sprintf( __( 'Block template "%s" is not registered.', 'gutenberg' ), $template_name ), + /* translators: %s: template name. */ + sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ), '6.7.0' ); - return false; + return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.' ) ); } $unregistered_block_template = $this->registered_block_templates[ $template_name ]; From 1b7292b3e63a83a97e30b0e3e49e561b6ebfe437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 13:43:26 +0200 Subject: [PATCH 06/49] Use wp_parse_args() instead of custom checks --- lib/class-wp-block-templates-registry.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php index 64994ff873506e..cae555fe4e8763 100644 --- a/lib/class-wp-block-templates-registry.php +++ b/lib/class-wp-block-templates-registry.php @@ -184,9 +184,14 @@ public function get_by_query( $query = array() ) { return array(); } - $slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); - $slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array(); - $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; + $query = wp_parse_args( $query, array( + 'slug__in' => array(), + 'slug__not_in' => array(), + 'post_type' => '', + ) ); + $slugs_to_include = $query['slug__in']; + $slugs_to_skip = $query['slug__not_in']; + $post_type = $query['post_type']; foreach ( $all_templates as $template_name => $template ) { if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include, true ) ) { From 474cd8a74301c149d7c5fd2f7067a7cafc3109fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 13:47:06 +0200 Subject: [PATCH 07/49] Add back code removed by mistake in a previous commit --- .../class-gutenberg-rest-templates-controller-6-7.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index b9d38d0a65f1e3..759afee14f8a24 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -93,7 +93,7 @@ public function prepare_item_for_response( $item, $request ) { * @return string Original source of the template one of theme, plugin, site, or user. */ private static function get_wp_templates_original_source_field( $template_object ) { - if ( 'wp_template' === $template_object->type ) { + if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { // Added by theme. // Template originally provided by a theme, but customized by a user. // Templates originally didn't have the 'origin' field so identify From 9c1016c5c311fc86a48c18a36d5888c020a7d99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 13:48:27 +0200 Subject: [PATCH 08/49] Add @core-merge comment specifying a function hasn't been modified --- .../class-gutenberg-rest-templates-controller-6-7.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 759afee14f8a24..0d145034fb2e6c 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -92,6 +92,7 @@ public function prepare_item_for_response( $item, $request ) { * @param WP_Block_Template $template_object Template instance. * @return string Original source of the template one of theme, plugin, site, or user. */ + // @core-merge: Nothing has changed in this function, the only reason to include it here is that it's a private function. private static function get_wp_templates_original_source_field( $template_object ) { if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { // Added by theme. From d31655f23070391c6d9657c51e41062b3ef514bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 14:01:26 +0200 Subject: [PATCH 09/49] Use wp_get_active_and_valid_plugins() instead of get_plugins() to retrieve the site plugins --- ...-gutenberg-rest-templates-controller-6-7.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 0d145034fb2e6c..bad6c0544cd630 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -147,13 +147,20 @@ private static function get_wp_templates_author_text_field( $template_object ) { $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); return empty( $theme_name ) ? $template_object->theme : $theme_name; case 'plugin': - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - $plugins = get_plugins(); + $plugins = wp_get_active_and_valid_plugins(); // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates. $plugin_name = isset( $template_object->plugin ) ? $template_object->plugin . '/' . $template_object->plugin : $template_object->theme; - $plugin = $plugins[ plugin_basename( sanitize_text_field( $plugin_name . '.php' ) ) ]; + $plugin_path = plugin_basename( sanitize_text_field( $plugin_name . '.php' ) ); + + $plugin = null; + foreach ( $plugins as $plugin_file ) { + if ( plugin_basename( $plugin_file ) === $plugin_path ) { + $plugin_data = get_plugin_data( $plugin_file ); + $plugin = $plugin_data; + break; + } + } + return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; // @core-merge: End of changes to merge in core. case 'site': From d9d7bf5f7e034c2814addc3a15bba08ca4f6d45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 14:29:31 +0200 Subject: [PATCH 10/49] Refactor how we get the plugin name so it works with plugins that follow the format plugin-slug/whatever-file-name-they-want-to.php --- ...utenberg-rest-templates-controller-6-7.php | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index bad6c0544cd630..a9eb8ee18959ce 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -139,7 +139,6 @@ private static function get_wp_templates_original_source_field( $template_object * @param WP_Block_Template $template_object Template instance. * @return string Human readable text for the author. */ - // @core-merge: Nothing has changed in this function, the only reason to include it here is that it's a private function. private static function get_wp_templates_author_text_field( $template_object ) { $original_source = self::get_wp_templates_original_source_field( $template_object ); switch ( $original_source ) { @@ -147,22 +146,35 @@ private static function get_wp_templates_author_text_field( $template_object ) { $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); return empty( $theme_name ) ? $template_object->theme : $theme_name; case 'plugin': - $plugins = wp_get_active_and_valid_plugins(); // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates. - $plugin_name = isset( $template_object->plugin ) ? $template_object->plugin . '/' . $template_object->plugin : $template_object->theme; - $plugin_path = plugin_basename( sanitize_text_field( $plugin_name . '.php' ) ); - - $plugin = null; - foreach ( $plugins as $plugin_file ) { - if ( plugin_basename( $plugin_file ) === $plugin_path ) { - $plugin_data = get_plugin_data( $plugin_file ); - $plugin = $plugin_data; - break; + if ( isset( $template_object->plugin ) ) { + $plugins = wp_get_active_and_valid_plugins(); + + foreach ( $plugins as $plugin_file ) { + $plugin_basename = plugin_basename( $plugin_file ); + // Split basename by '/' to get the plugin slug. + list( $plugin_slug, ) = explode( '/', $plugin_basename ); + + if ( $plugin_slug === $template_object->plugin ) { + $plugin_data = get_plugin_data( $plugin_file ); + + if ( ! empty( $plugin_data['Name'] ) ) { + return $plugin_data['Name']; + } + + break; + } } } + // @core-merge: End of changes to merge in core. + /* + * Fallback to the theme name if the plugin is not defined. That's needed to keep backwards + * compatibility with templates that were registered before the plugin attribute was added. + */ + $plugins = get_plugins(); + $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ]; return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; - // @core-merge: End of changes to merge in core. case 'site': return get_bloginfo( 'name' ); case 'user': From 69e340cc095872551b0c4aa630a67f5570bfc0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 16:22:06 +0200 Subject: [PATCH 11/49] Fix registered templates beind 'removable' instead of 'revertable' --- packages/edit-site/src/utils/is-template-removable.js | 2 +- packages/edit-site/src/utils/is-template-revertable.js | 3 ++- packages/editor/src/dataviews/actions/reset-post.tsx | 2 +- packages/editor/src/dataviews/actions/utils.ts | 4 +++- packages/editor/src/dataviews/types.ts | 1 + packages/editor/src/store/utils/is-template-revertable.js | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/edit-site/src/utils/is-template-removable.js b/packages/edit-site/src/utils/is-template-removable.js index 6566c6e4c1109b..a34c58c33478bb 100644 --- a/packages/edit-site/src/utils/is-template-removable.js +++ b/packages/edit-site/src/utils/is-template-removable.js @@ -16,7 +16,7 @@ export default function isTemplateRemovable( template ) { return ( template.source === TEMPLATE_ORIGINS.custom && - template.origin !== 'plugin' && + template.origin !== TEMPLATE_ORIGINS.plugin && ! template.has_theme_file ); } diff --git a/packages/edit-site/src/utils/is-template-revertable.js b/packages/edit-site/src/utils/is-template-revertable.js index e9b65df0580c41..f3af3372319cd5 100644 --- a/packages/edit-site/src/utils/is-template-revertable.js +++ b/packages/edit-site/src/utils/is-template-revertable.js @@ -16,7 +16,8 @@ export default function isTemplateRevertable( template ) { /* eslint-disable camelcase */ return ( template?.source === TEMPLATE_ORIGINS.custom && - ( template?.origin === 'plugin' || template?.has_theme_file ) + ( template?.origin === TEMPLATE_ORIGINS.plugin || + template?.has_theme_file ) ); /* eslint-enable camelcase */ } diff --git a/packages/editor/src/dataviews/actions/reset-post.tsx b/packages/editor/src/dataviews/actions/reset-post.tsx index 59199555ddd4db..bb58522bcc9920 100644 --- a/packages/editor/src/dataviews/actions/reset-post.tsx +++ b/packages/editor/src/dataviews/actions/reset-post.tsx @@ -32,7 +32,7 @@ const resetPost: Action< Post > = { return ( isTemplateOrTemplatePart( item ) && item?.source === TEMPLATE_ORIGINS.custom && - item?.has_theme_file + ( item?.origin === TEMPLATE_ORIGINS.plugin || item?.has_theme_file ) ); }, icon: backup, diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts index 7da1f71728365b..da5ae6bdfdc2b5 100644 --- a/packages/editor/src/dataviews/actions/utils.ts +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -57,6 +57,8 @@ export function isTemplateRemovable( template: Template | TemplatePart ) { return ( [ template.source, template.source ].includes( TEMPLATE_ORIGINS.custom - ) && ! template.has_theme_file + ) && + template?.origin !== TEMPLATE_ORIGINS.plugin && + ! template.has_theme_file ); } diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 514953d6691290..820c2b864c1365 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -41,6 +41,7 @@ export interface Template extends CommonPost { export interface TemplatePart extends CommonPost { type: 'wp_template_part'; source: string; + origin: string; has_theme_file: boolean; id: string; area: string; diff --git a/packages/editor/src/store/utils/is-template-revertable.js b/packages/editor/src/store/utils/is-template-revertable.js index e311667aa9a776..ff205ca97c46d6 100644 --- a/packages/editor/src/store/utils/is-template-revertable.js +++ b/packages/editor/src/store/utils/is-template-revertable.js @@ -18,7 +18,7 @@ export default function isTemplateRevertable( templateOrTemplatePart ) { return ( templateOrTemplatePart.source === TEMPLATE_ORIGINS.custom && - ( templateOrTemplatePart?.origin === 'plugin' || + ( templateOrTemplatePart?.origin === TEMPLATE_ORIGINS.plugin || templateOrTemplatePart?.has_theme_file ) ); } From 736b0a8d04f5566e1d4e39afedfc00eea2aa0f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 4 Jul 2024 17:11:26 +0200 Subject: [PATCH 12/49] PHP linting --- lib/class-wp-block-templates-registry.php | 27 +++++++++++-------- ...utenberg-rest-templates-controller-6-7.php | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php index cae555fe4e8763..2ba5d43a2d0e1d 100644 --- a/lib/class-wp-block-templates-registry.php +++ b/lib/class-wp-block-templates-registry.php @@ -54,7 +54,7 @@ public function register( $template_name, $args = array() ) { __( 'Block template names must be a string.', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_name_no_string', __( 'Block template names must be a string.' ) ); + return new WP_Error( 'template_name_no_string', __( 'Block template names must be a string.', 'gutenberg' ) ); } if ( preg_match( '/[A-Z]+/', $template_name ) ) { @@ -63,7 +63,7 @@ public function register( $template_name, $args = array() ) { __( 'Block template names must not contain uppercase characters.', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_name_no_uppercase', __( 'Block template names must not contain uppercase characters.' ) ); + return new WP_Error( 'template_name_no_uppercase', __( 'Block template names must not contain uppercase characters.', 'gutenberg' ) ); } $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; @@ -73,7 +73,7 @@ public function register( $template_name, $args = array() ) { __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_no_prefix', __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template' ) ); + return new WP_Error( 'template_no_prefix', __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ) ); } if ( $this->is_registered( $template_name ) ) { @@ -83,7 +83,8 @@ public function register( $template_name, $args = array() ) { sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ), '6.7.0' ); - return new WP_Error( 'template_already_registered', __( 'Template "%s" is already registered.' ) ); + /* translators: %s: Template name. */ + return new WP_Error( 'template_already_registered', __( 'Template "%s" is already registered.', 'gutenberg' ) ); } if ( ! $template ) { @@ -184,11 +185,14 @@ public function get_by_query( $query = array() ) { return array(); } - $query = wp_parse_args( $query, array( - 'slug__in' => array(), - 'slug__not_in' => array(), - 'post_type' => '', - ) ); + $query = wp_parse_args( + $query, + array( + 'slug__in' => array(), + 'slug__not_in' => array(), + 'post_type' => '', + ) + ); $slugs_to_include = $query['slug__in']; $slugs_to_skip = $query['slug__not_in']; $post_type = $query['post_type']; @@ -234,11 +238,12 @@ public function unregister( $template_name ) { if ( ! $this->is_registered( $template_name ) ) { _doing_it_wrong( __METHOD__, - /* translators: %s: template name. */ + /* translators: %s: Template name. */ sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ), '6.7.0' ); - return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.' ) ); + /* translators: %s: Template name. */ + return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) ); } $unregistered_block_template = $this->registered_block_templates[ $template_name ]; diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index a9eb8ee18959ce..37de68f5f82408 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -148,7 +148,7 @@ private static function get_wp_templates_author_text_field( $template_object ) { case 'plugin': // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates. if ( isset( $template_object->plugin ) ) { - $plugins = wp_get_active_and_valid_plugins(); + $plugins = wp_get_active_and_valid_plugins(); foreach ( $plugins as $plugin_file ) { $plugin_basename = plugin_basename( $plugin_file ); From 5e6719f8bf01503e6eebf15cf620ae6a7c4a23fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Mon, 8 Jul 2024 16:13:29 +0200 Subject: [PATCH 13/49] Rely on template.plugin instead of template.origin to identify plugin templates, so we correctly handle plugin deactivation --- ...utenberg-rest-templates-controller-6-7.php | 187 ++++++++++++++++++ lib/compat/wordpress-6.7/compat.php | 26 +++ .../core-data/src/entity-types/wp-template.ts | 4 + .../src/utils/is-template-removable.js | 2 +- .../src/utils/is-template-revertable.js | 3 +- .../src/dataviews/actions/reset-post.tsx | 2 +- .../editor/src/dataviews/actions/utils.ts | 2 +- packages/editor/src/dataviews/types.ts | 1 + .../src/store/utils/is-template-revertable.js | 2 +- 9 files changed, 223 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 37de68f5f82408..3b18f7bf953091 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -10,6 +10,186 @@ * */ class Gutenberg_REST_Templates_Controller_6_7 extends Gutenberg_REST_Templates_Controller_6_6 { + /** + * Retrieves the block type' schema, conforming to JSON Schema. + * + * @since 5.8.0 + * @since 5.9.0 Added `'area'`. + * @since 6.7.0 Added `'plugin'`. + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => $this->post_type, + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'ID of template.' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'slug' => array( + 'description' => __( 'Unique slug identifying the template.' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + 'required' => true, + 'minLength' => 1, + 'pattern' => '[a-zA-Z0-9_\%-]+', + ), + 'theme' => array( + 'description' => __( 'Theme identifier for the template.' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + ), + // @core-merge: Add plugin property to the schema. + 'plugin' => array( + 'description' => __( 'Plugin that registered the template.' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + ), + // @core-merge: End of changes. + 'type' => array( + 'description' => __( 'Type of template.' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + ), + 'source' => array( + 'description' => __( 'Source of template' ), + 'type' => 'string', + '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' ), + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'Content for the template, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'block_version' => array( + 'description' => __( 'Version of the content block format used by the template.' ), + 'type' => 'integer', + 'context' => array( 'edit' ), + 'readonly' => true, + ), + ), + ), + 'title' => array( + 'description' => __( 'Title of template.' ), + 'type' => array( 'object', 'string' ), + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'Title for the template, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'rendered' => array( + 'description' => __( 'HTML title for the template, transformed for display.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ), + 'description' => array( + 'description' => __( 'Description of template.' ), + 'type' => 'string', + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + ), + 'status' => array( + 'description' => __( 'Status of template.' ), + 'type' => 'string', + 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), + 'default' => 'publish', + 'context' => array( 'embed', 'view', 'edit' ), + ), + 'wp_id' => array( + 'description' => __( 'Post ID.' ), + 'type' => 'integer', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'has_theme_file' => array( + 'description' => __( 'Theme file exists.' ), + 'type' => 'bool', + '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' ), + ), + 'modified' => array( + 'description' => __( "The date the template was last modified, in the site's timezone." ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'author_text' => array( + 'type' => 'string', + 'description' => __( 'Human readable text for the author.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'original_source' => array( + 'description' => __( 'Where the template originally comes from e.g. \'theme\'' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + 'enum' => array( + 'theme', + 'plugin', + 'site', + 'user', + ), + ), + ), + ); + + if ( 'wp_template' === $this->post_type ) { + $schema['properties']['is_custom'] = array( + 'description' => __( 'Whether a template is a custom template.' ), + 'type' => 'bool', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + } + + if ( 'wp_template_part' === $this->post_type ) { + $schema['properties']['area'] = array( + 'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + ); + } + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + /** * Returns the given template * @@ -61,6 +241,13 @@ public function prepare_item_for_response( $item, $request ) { $data['origin'] = 'plugin'; } + if ( rest_is_field_included( 'plugin', $fields ) ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug ); + if ( $registered_template ) { + $data['plugin'] = $registered_template->plugin; + } + } + if ( rest_is_field_included( 'author_text', $fields ) ) { $data['author_text'] = $this->get_wp_templates_author_text_field( $template ); } diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index 4d8f8074a2bef8..48707225797986 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -23,6 +23,14 @@ * the registry. */ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $template_type ) { + // Add `plugin` property to templates registered by a plugin. + foreach ( $query_result as $key => $value ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); + if ( $registered_template ) { + $query_result[ $key ]->plugin = $registered_template->plugin; + } + } + if ( ! isset( $query['wp_id'] ) ) { $template_files = _gutenberg_get_block_templates_files( $template_type, $query ); @@ -48,6 +56,24 @@ function ( $registered_template ) use ( $template_files ) { } add_filter( 'get_block_templates', '_gutenberg_add_block_templates_from_registry', 10, 3 ); +/** + * Hooks into `get_block_template` to add the `plugin` property when necessary. + * + * @param [WP_Block_Template|null] $block_template The found block template, or null if there isn’t one. + * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was reigstered by a plugin. + */ +function _gutenberg_add_block_template_plugin_attribute( $block_template ) { + if ( $block_template ) { + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + if ( $registered_template ) { + $block_template->plugin = $registered_template->plugin; + } + } + + return $block_template; +} +add_filter( 'get_block_template', '_gutenberg_add_block_template_plugin_attribute', 10, 1 ); + /** * Hooks into `get_block_file_template` so templates from the registry are also returned. * diff --git a/packages/core-data/src/entity-types/wp-template.ts b/packages/core-data/src/entity-types/wp-template.ts index ac6db09035f193..70d3e40c295dcf 100644 --- a/packages/core-data/src/entity-types/wp-template.ts +++ b/packages/core-data/src/entity-types/wp-template.ts @@ -73,6 +73,10 @@ declare module './base-entity-records' { * Post ID. */ wp_id: number; + /** + * Plugin that registered the template. + */ + plugin?: string; /** * Theme file exists. */ diff --git a/packages/edit-site/src/utils/is-template-removable.js b/packages/edit-site/src/utils/is-template-removable.js index a34c58c33478bb..f81cb74b022e73 100644 --- a/packages/edit-site/src/utils/is-template-removable.js +++ b/packages/edit-site/src/utils/is-template-removable.js @@ -16,7 +16,7 @@ export default function isTemplateRemovable( template ) { return ( template.source === TEMPLATE_ORIGINS.custom && - template.origin !== TEMPLATE_ORIGINS.plugin && + ! Boolean( template.plugin ) && ! template.has_theme_file ); } diff --git a/packages/edit-site/src/utils/is-template-revertable.js b/packages/edit-site/src/utils/is-template-revertable.js index f3af3372319cd5..42413b06cd48ec 100644 --- a/packages/edit-site/src/utils/is-template-revertable.js +++ b/packages/edit-site/src/utils/is-template-revertable.js @@ -16,8 +16,7 @@ export default function isTemplateRevertable( template ) { /* eslint-disable camelcase */ return ( template?.source === TEMPLATE_ORIGINS.custom && - ( template?.origin === TEMPLATE_ORIGINS.plugin || - template?.has_theme_file ) + ( Boolean( template?.plugin ) || template?.has_theme_file ) ); /* eslint-enable camelcase */ } diff --git a/packages/editor/src/dataviews/actions/reset-post.tsx b/packages/editor/src/dataviews/actions/reset-post.tsx index bb58522bcc9920..90c981f3bfdcca 100644 --- a/packages/editor/src/dataviews/actions/reset-post.tsx +++ b/packages/editor/src/dataviews/actions/reset-post.tsx @@ -32,7 +32,7 @@ const resetPost: Action< Post > = { return ( isTemplateOrTemplatePart( item ) && item?.source === TEMPLATE_ORIGINS.custom && - ( item?.origin === TEMPLATE_ORIGINS.plugin || item?.has_theme_file ) + ( Boolean( item?.plugin ) || item?.has_theme_file ) ); }, icon: backup, diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts index da5ae6bdfdc2b5..c9d363d4551154 100644 --- a/packages/editor/src/dataviews/actions/utils.ts +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -58,7 +58,7 @@ export function isTemplateRemovable( template: Template | TemplatePart ) { [ template.source, template.source ].includes( TEMPLATE_ORIGINS.custom ) && - template?.origin !== TEMPLATE_ORIGINS.plugin && + ! Boolean( template?.plugin ) && ! template.has_theme_file ); } diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 820c2b864c1365..af640649a31de9 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -42,6 +42,7 @@ export interface TemplatePart extends CommonPost { type: 'wp_template_part'; source: string; origin: string; + plugin?: string; has_theme_file: boolean; id: string; area: string; diff --git a/packages/editor/src/store/utils/is-template-revertable.js b/packages/editor/src/store/utils/is-template-revertable.js index ff205ca97c46d6..2cb674920e3e4c 100644 --- a/packages/editor/src/store/utils/is-template-revertable.js +++ b/packages/editor/src/store/utils/is-template-revertable.js @@ -18,7 +18,7 @@ export default function isTemplateRevertable( templateOrTemplatePart ) { return ( templateOrTemplatePart.source === TEMPLATE_ORIGINS.custom && - ( templateOrTemplatePart?.origin === TEMPLATE_ORIGINS.plugin || + ( Boolean( templateOrTemplatePart?.plugin ) || templateOrTemplatePart?.has_theme_file ) ); } From 83430f54b6319c6096e662d06dbdc1818584749e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 13:40:45 +0200 Subject: [PATCH 14/49] Replace more instances of 'block templates' with simply 'templates' --- lib/block-templates.php | 2 +- lib/class-wp-block-templates-registry.php | 74 +++++++++---------- ...utenberg-rest-templates-controller-6-7.php | 4 +- lib/compat/wordpress-6.7/compat.php | 10 +-- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/lib/block-templates.php b/lib/block-templates.php index c866d4b21323ba..1f507d4d054899 100644 --- a/lib/block-templates.php +++ b/lib/block-templates.php @@ -6,5 +6,5 @@ */ function gutenberg_register_template( $template_name, $args = array() ) { - return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args ); + return WP_Templates_Registry::get_instance()->register( $template_name, $args ); } diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php index 2ba5d43a2d0e1d..356131dc033545 100644 --- a/lib/class-wp-block-templates-registry.php +++ b/lib/class-wp-block-templates-registry.php @@ -1,44 +1,44 @@ $instance` pairs. + * Registered templates, as `$name => $instance` pairs. * * @since 6.7.0 - * @var WP_Block_Template[] $registered_block_templates Registered block templates. + * @var WP_Block_Template[] $registered_block_templates Registered templates. */ - private $registered_block_templates = array(); + private $registered_templates = array(); /** * Container for the main instance of the class. * * @since 6.7.0 - * @var WP_Block_Templates_Registry|null + * @var WP_Templates_Registry|null */ private static $instance = null; /** - * Registers a block template. + * Registers a template. * * @since 6.7.0 * - * @param string|WP_Block_Template $template_name Block template name including namespace, or alternatively + * @param string|WP_Block_Template $template_name Template name including namespace, or alternatively * a complete WP_Block_Template instance. In case a WP_Block_Template * is provided, the $args parameter will be ignored. - * @param array $args Optional. Array of block template arguments. - * @return WP_Block_Template|false The registered block template on success, or false on failure. + * @param array $args Optional. Array of template arguments. + * @return WP_Block_Template|false The registered template on success, or false on failure. */ public function register( $template_name, $args = array() ) { @@ -51,29 +51,29 @@ public function register( $template_name, $args = array() ) { if ( ! is_string( $template_name ) ) { _doing_it_wrong( __METHOD__, - __( 'Block template names must be a string.', 'gutenberg' ), + __( 'Template names must be a string.', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_name_no_string', __( 'Block template names must be a string.', 'gutenberg' ) ); + return new WP_Error( 'template_name_no_string', __( 'Template names must be a string.', 'gutenberg' ) ); } if ( preg_match( '/[A-Z]+/', $template_name ) ) { _doing_it_wrong( __METHOD__, - __( 'Block template names must not contain uppercase characters.', 'gutenberg' ), + __( 'Template names must not contain uppercase characters.', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_name_no_uppercase', __( 'Block template names must not contain uppercase characters.', 'gutenberg' ) ); + return new WP_Error( 'template_name_no_uppercase', __( 'Template names must not contain uppercase characters.', 'gutenberg' ) ); } $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; if ( ! preg_match( $name_matcher, $template_name ) ) { _doing_it_wrong( __METHOD__, - __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), + __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_no_prefix', __( 'Block template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ) ); + return new WP_Error( 'template_no_prefix', __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ) ); } if ( $this->is_registered( $template_name ) ) { @@ -109,20 +109,20 @@ public function register( $template_name, $args = array() ) { $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; } - $this->registered_block_templates[ $template_name ] = $template; + $this->registered_templates[ $template_name ] = $template; return $template; } /** - * Retrieves all registered block templates. + * Retrieves all registered templates. * * @since 6.7.0 * - * @return WP_Block_Template[]|false Associative array of `$block_template_name => $block_template` pairs. + * @return WP_Block_Template[]|false Associative array of `$template_name => $template` pairs. */ public function get_all_registered() { - return $this->registered_block_templates; + return $this->registered_templates; } /** @@ -130,15 +130,15 @@ public function get_all_registered() { * * @since 6.7.0 * - * @param string $template_name Block template name including namespace. - * @return WP_Block_Template|null|false The registered block template, or null if it is not registered. + * @param string $template_name Template name including namespace. + * @return WP_Block_Template|null|false The registered template, or null if it is not registered. */ public function get_registered( $template_name ) { if ( ! $this->is_registered( $template_name ) ) { return null; } - return $this->registered_block_templates[ $template_name ]; + return $this->registered_templates[ $template_name ]; } /** @@ -147,7 +147,7 @@ public function get_registered( $template_name ) { * @since 6.7.0 * * @param string $template_slug Slug of the template. - * @return WP_Block_Template|null The registered block template, or null if it is not registered. + * @return WP_Block_Template|null The registered template, or null if it is not registered. */ public function get_by_slug( $template_slug ) { $all_templates = $this->get_all_registered(); @@ -166,7 +166,7 @@ public function get_by_slug( $template_slug ) { } /** - * Retrieves registered block templates matching a query. + * Retrieves registered templates matching a query. * * @since 6.7.0 * @@ -215,24 +215,24 @@ public function get_by_query( $query = array() ) { } /** - * Checks if a block template is registered. + * Checks if a template is registered. * * @since 6.7.0 * - * @param string $template_name Block template name including namespace. + * @param string $template_name Template name including namespace. * @return bool True if the template is registered, false otherwise. */ public function is_registered( $template_name ) { - return isset( $this->registered_block_templates[ $template_name ] ); + return isset( $this->registered_templates[ $template_name ] ); } /** - * Unregisters a block template. + * Unregisters a template. * * @since 6.7.0 * - * @param string $name Block template name including namespace. - * @return WP_Block_Template|false The unregistered block template on success, or false on failure. + * @param string $name Template name including namespace. + * @return WP_Block_Template|false The unregistered template on success, or false on failure. */ public function unregister( $template_name ) { if ( ! $this->is_registered( $template_name ) ) { @@ -246,10 +246,10 @@ public function unregister( $template_name ) { return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) ); } - $unregistered_block_template = $this->registered_block_templates[ $template_name ]; - unset( $this->registered_block_templates[ $template_name ] ); + $unregistered_template = $this->registered_templates[ $template_name ]; + unset( $this->registered_templates[ $template_name ] ); - return $unregistered_block_template; + return $unregistered_template; } /** @@ -259,7 +259,7 @@ public function unregister( $template_name ) { * * @since 6.7.0 * - * @return WP_Block_Templates_Registry The main instance. + * @return WP_Templates_Registry The main instance. */ public static function get_instance() { if ( null === self::$instance ) { diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 3b18f7bf953091..4dcb4b08c2a1cc 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -202,7 +202,7 @@ public function get_item( $request ) { // @core-merge: Add a special case for plugin templates. } elseif ( isset( $request['source'] ) && 'plugin' === $request['source'] ) { list( , $slug ) = explode( '//', $request['id'] ); - $template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug ); + $template = WP_Templates_Registry::get_instance()->get_by_slug( $slug ); // @core-merge: End of changes to merge in core. } else { $template = get_block_template( $request['id'], $this->post_type ); @@ -242,7 +242,7 @@ public function prepare_item_for_response( $item, $request ) { } if ( rest_is_field_included( 'plugin', $fields ) ) { - $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug ); + $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug ); if ( $registered_template ) { $data['plugin'] = $registered_template->plugin; } diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index 48707225797986..cdc536c2157cb1 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -25,7 +25,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $template_type ) { // Add `plugin` property to templates registered by a plugin. foreach ( $query_result as $key => $value ) { - $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); + $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); if ( $registered_template ) { $query_result[ $key ]->plugin = $registered_template->plugin; } @@ -37,7 +37,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t /* * 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 ); + $registered_templates = WP_Templates_Registry::get_instance()->get_by_query( $query ); $matching_registered_templates = array_filter( $registered_templates, function ( $registered_template ) use ( $template_files ) { @@ -64,7 +64,7 @@ function ( $registered_template ) use ( $template_files ) { */ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { if ( $block_template ) { - $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; } @@ -83,7 +83,7 @@ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { */ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id ) { if ( $block_template ) { - $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; } @@ -97,6 +97,6 @@ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id } list( , $slug ) = $parts; - return WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug ); + return WP_Templates_Registry::get_instance()->get_by_slug( $slug ); } add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 2 ); From 934d358945dcccd45dfb53998e93046802d8e4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 13:47:48 +0200 Subject: [PATCH 15/49] Avoid usage of unset() --- lib/class-wp-block-templates-registry.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-block-templates-registry.php index 356131dc033545..f11165f8455461 100644 --- a/lib/class-wp-block-templates-registry.php +++ b/lib/class-wp-block-templates-registry.php @@ -197,21 +197,24 @@ public function get_by_query( $query = array() ) { $slugs_to_skip = $query['slug__not_in']; $post_type = $query['post_type']; + $matching_templates = array(); foreach ( $all_templates as $template_name => $template ) { if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include, true ) ) { - unset( $all_templates[ $template_name ] ); + continue; } if ( ! empty( $slugs_to_skip ) && in_array( $template->slug, $slugs_to_skip, true ) ) { - unset( $all_templates[ $template_name ] ); + continue; } if ( ! empty( $post_type ) && ! in_array( $post_type, $template->post_types, true ) ) { - unset( $all_templates[ $template_name ] ); + continue; } + + $matching_templates[ $template_name ] = $template; } - return $all_templates; + return $matching_templates; } /** From e2006a3cf427ad59bf80035bc58de28bf1421fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 14:56:23 +0200 Subject: [PATCH 16/49] Fix warning when trying to retrieve the wrong plugin --- ...lass-gutenberg-rest-templates-controller-6-7.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 4dcb4b08c2a1cc..35099fd30fb224 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -353,15 +353,18 @@ private static function get_wp_templates_author_text_field( $template_object ) { } } } - // @core-merge: End of changes to merge in core. /* - * Fallback to the theme name if the plugin is not defined. That's needed to keep backwards + * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards * compatibility with templates that were registered before the plugin attribute was added. */ - $plugins = get_plugins(); - $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ]; - return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; + $plugins = get_plugins(); + $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); + if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { + return $plugins[ $plugin_basename ]['Name']; + } + return $template_object->theme; + // @core-merge: End of changes to merge in core. case 'site': return get_bloginfo( 'name' ); case 'user': From 93d1698d07e2aea80c94e31a9ab55e746d25d000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 14:58:43 +0200 Subject: [PATCH 17/49] Update comments format --- ...utenberg-rest-templates-controller-6-7.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 35099fd30fb224..16c69569494315 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -279,14 +279,16 @@ public function prepare_item_for_response( $item, $request ) { * @param WP_Block_Template $template_object Template instance. * @return string Original source of the template one of theme, plugin, site, or user. */ - // @core-merge: Nothing has changed in this function, the only reason to include it here is that it's a private function. + // @core-merge: Only the comments format (from inline to multi-line) has changed in this file. private static function get_wp_templates_original_source_field( $template_object ) { if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { - // Added by theme. - // Template originally provided by a theme, but customized by a user. - // Templates originally didn't have the 'origin' field so identify - // older customized templates by checking for no origin and a 'theme' - // or 'custom' source. + /* + * Added by theme. + * Template originally provided by a theme, but customized by a user. + * Templates originally didn't have the 'origin' field so identify + * older customized templates by checking for no origin and a 'theme' + * or 'custom' source. + */ if ( $template_object->has_theme_file && ( 'theme' === $template_object->origin || ( empty( $template_object->origin ) && in_array( @@ -307,10 +309,12 @@ private static function get_wp_templates_original_source_field( $template_object return 'plugin'; } - // Added by site. - // Template was created from scratch, but has no author. Author support - // was only added to templates in WordPress 5.9. Fallback to showing the - // site logo and title. + /* + * Added by site. + * Template was created from scratch, but has no author. Author support + * was only added to templates in WordPress 5.9. Fallback to showing the + * site logo and title. + */ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { return 'site'; } From 3f16162ff4f3bd60a968dcc66707efc7758bdc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 15:24:50 +0200 Subject: [PATCH 18/49] Refactor how we add the 'plugin' property --- ...utenberg-rest-templates-controller-6-7.php | 180 ------------------ lib/compat/wordpress-6.7/rest-api.php | 42 ++++ 2 files changed, 42 insertions(+), 180 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 16c69569494315..2eca807c24cc5c 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -10,186 +10,6 @@ * */ class Gutenberg_REST_Templates_Controller_6_7 extends Gutenberg_REST_Templates_Controller_6_6 { - /** - * Retrieves the block type' schema, conforming to JSON Schema. - * - * @since 5.8.0 - * @since 5.9.0 Added `'area'`. - * @since 6.7.0 Added `'plugin'`. - * - * @return array Item schema data. - */ - public function get_item_schema() { - if ( $this->schema ) { - return $this->add_additional_fields_schema( $this->schema ); - } - - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => $this->post_type, - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'ID of template.' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'slug' => array( - 'description' => __( 'Unique slug identifying the template.' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - 'required' => true, - 'minLength' => 1, - 'pattern' => '[a-zA-Z0-9_\%-]+', - ), - 'theme' => array( - 'description' => __( 'Theme identifier for the template.' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - ), - // @core-merge: Add plugin property to the schema. - 'plugin' => array( - 'description' => __( 'Plugin that registered the template.' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - ), - // @core-merge: End of changes. - 'type' => array( - 'description' => __( 'Type of template.' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'source' => array( - 'description' => __( 'Source of template' ), - 'type' => 'string', - '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' ), - 'default' => '', - 'context' => array( 'embed', 'view', 'edit' ), - 'properties' => array( - 'raw' => array( - 'description' => __( 'Content for the template, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'block_version' => array( - 'description' => __( 'Version of the content block format used by the template.' ), - 'type' => 'integer', - 'context' => array( 'edit' ), - 'readonly' => true, - ), - ), - ), - 'title' => array( - 'description' => __( 'Title of template.' ), - 'type' => array( 'object', 'string' ), - 'default' => '', - 'context' => array( 'embed', 'view', 'edit' ), - 'properties' => array( - 'raw' => array( - 'description' => __( 'Title for the template, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'rendered' => array( - 'description' => __( 'HTML title for the template, transformed for display.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - ), - ), - 'description' => array( - 'description' => __( 'Description of template.' ), - 'type' => 'string', - 'default' => '', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'status' => array( - 'description' => __( 'Status of template.' ), - 'type' => 'string', - 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), - 'default' => 'publish', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'wp_id' => array( - 'description' => __( 'Post ID.' ), - 'type' => 'integer', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'has_theme_file' => array( - 'description' => __( 'Theme file exists.' ), - 'type' => 'bool', - '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' ), - ), - 'modified' => array( - 'description' => __( "The date the template was last modified, in the site's timezone." ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'author_text' => array( - 'type' => 'string', - 'description' => __( 'Human readable text for the author.' ), - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'original_source' => array( - 'description' => __( 'Where the template originally comes from e.g. \'theme\'' ), - 'type' => 'string', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - 'enum' => array( - 'theme', - 'plugin', - 'site', - 'user', - ), - ), - ), - ); - - if ( 'wp_template' === $this->post_type ) { - $schema['properties']['is_custom'] = array( - 'description' => __( 'Whether a template is a custom template.' ), - 'type' => 'bool', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ); - } - - if ( 'wp_template_part' === $this->post_type ) { - $schema['properties']['area'] = array( - 'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - ); - } - - $this->schema = $schema; - - return $this->add_additional_fields_schema( $this->schema ); - } - /** * Returns the given template * diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 657ab03e3acc03..5c3971faf550a1 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -49,3 +49,45 @@ function wp_api_template_registry( $args, $post_type ) { } } add_filter( 'register_post_type_args', 'wp_api_template_registry', 10, 2 ); + +/** + * Registers additional fields for wp_template rest api. + * + * @access private + * @internal + * + * @param array $template_object Template object. + * @return string Human readable text for the author. + */ +function _gutenberg_get_wp_templates_plugin_field( $template_object ) { + if ( $template_object ) { + $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] ); + if ( $registered_template ) { + return $registered_template->plugin; + } + } + + return; +} + +/** + * Adds `plugin` fields to WP_REST_Templates_Controller class. + */ +function gutenberg_register_wp_rest_templates_controller_plugin_field() { + + register_rest_field( + 'wp_template', + 'plugin', + array( + 'get_callback' => '_gutenberg_get_wp_templates_plugin_field', + 'update_callback' => null, + 'schema' => array( + 'type' => 'string', + 'description' => __( 'Plugin that registered the template.', 'gutenberg' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); +} +add_action( 'rest_api_init', 'gutenberg_register_wp_rest_templates_controller_plugin_field' ); From d8b545f58f19b87e99557e318335ddfe1d6ba0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 15:32:35 +0200 Subject: [PATCH 19/49] Rename WP_Templates_Registry --- ...k-templates-registry.php => class-wp-templates-registry.php} | 0 lib/load.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{class-wp-block-templates-registry.php => class-wp-templates-registry.php} (100%) diff --git a/lib/class-wp-block-templates-registry.php b/lib/class-wp-templates-registry.php similarity index 100% rename from lib/class-wp-block-templates-registry.php rename to lib/class-wp-templates-registry.php diff --git a/lib/load.php b/lib/load.php index 54563d9e1cc900..4c429064054d1b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -144,7 +144,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/class-wp-theme-json-resolver-gutenberg.php'; require __DIR__ . '/class-wp-theme-json-schema-gutenberg.php'; require __DIR__ . '/class-wp-duotone-gutenberg.php'; -require __DIR__ . '/class-wp-block-templates-registry.php'; +require __DIR__ . '/class-wp-templates-registry.php'; require __DIR__ . '/blocks.php'; require __DIR__ . '/block-templates.php'; require __DIR__ . '/block-editor-settings.php'; From 8de5b4772f0d50d80f5984978dcc79197f7c7965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 15:34:54 +0200 Subject: [PATCH 20/49] Fix alignment --- lib/compat/wordpress-6.7/rest-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index 5c3971faf550a1..ae3b765449c291 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -81,7 +81,7 @@ function gutenberg_register_wp_rest_templates_controller_plugin_field() { array( 'get_callback' => '_gutenberg_get_wp_templates_plugin_field', 'update_callback' => null, - 'schema' => array( + 'schema' => array( 'type' => 'string', 'description' => __( 'Plugin that registered the template.', 'gutenberg' ), 'readonly' => true, From 64aad98b333d577ad7277d95ea3f1b9380e5f7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 16 Jul 2024 15:45:35 +0200 Subject: [PATCH 21/49] Remove has_theme_file=true property from plugin-registered templates --- lib/class-wp-templates-registry.php | 31 +++++++++---------- ...utenberg-rest-templates-controller-6-7.php | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index f11165f8455461..974cd935bb469d 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -91,22 +91,21 @@ public function register( $template_name, $args = array() ) { $theme_name = get_stylesheet(); $slug = isset( $args['slug'] ) ? $args['slug'] : explode( '//', $template_name )[1]; - $template = new WP_Block_Template(); - $template->id = $theme_name . '//' . $slug; - $template->theme = $theme_name; - $template->plugin = isset( $args['plugin'] ) ? $args['plugin'] : ''; - $template->author = null; - $template->content = isset( $args['content'] ) ? $args['content'] : ''; - $template->source = 'plugin'; - $template->slug = $slug; - $template->type = 'wp_template'; - $template->title = isset( $args['title'] ) ? $args['title'] : ''; - $template->description = isset( $args['description'] ) ? $args['description'] : ''; - $template->status = 'publish'; - $template->has_theme_file = true; - $template->origin = 'plugin'; - $template->is_custom = true; - $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; + $template = new WP_Block_Template(); + $template->id = $theme_name . '//' . $slug; + $template->theme = $theme_name; + $template->plugin = isset( $args['plugin'] ) ? $args['plugin'] : ''; + $template->author = null; + $template->content = isset( $args['content'] ) ? $args['content'] : ''; + $template->source = 'plugin'; + $template->slug = $slug; + $template->type = 'wp_template'; + $template->title = isset( $args['title'] ) ? $args['title'] : ''; + $template->description = isset( $args['description'] ) ? $args['description'] : ''; + $template->status = 'publish'; + $template->origin = 'plugin'; + $template->is_custom = true; + $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; } $this->registered_templates[ $template_name ] = $template; diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 2eca807c24cc5c..2c58de995156e5 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -125,7 +125,7 @@ private static function get_wp_templates_original_source_field( $template_object } // Added by plugin. - if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) { + if ( 'plugin' === $template_object->origin ) { return 'plugin'; } From e13c5a6b6bebc7c5563fd0c923fe8c1633885c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 12:47:25 +0200 Subject: [PATCH 22/49] Inline get_callback function --- lib/compat/wordpress-6.7/rest-api.php | 31 +++++++++------------------ 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index ae3b765449c291..c98f03c4257b17 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -50,26 +50,6 @@ function wp_api_template_registry( $args, $post_type ) { } add_filter( 'register_post_type_args', 'wp_api_template_registry', 10, 2 ); -/** - * Registers additional fields for wp_template rest api. - * - * @access private - * @internal - * - * @param array $template_object Template object. - * @return string Human readable text for the author. - */ -function _gutenberg_get_wp_templates_plugin_field( $template_object ) { - if ( $template_object ) { - $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] ); - if ( $registered_template ) { - return $registered_template->plugin; - } - } - - return; -} - /** * Adds `plugin` fields to WP_REST_Templates_Controller class. */ @@ -79,7 +59,16 @@ function gutenberg_register_wp_rest_templates_controller_plugin_field() { 'wp_template', 'plugin', array( - 'get_callback' => '_gutenberg_get_wp_templates_plugin_field', + 'get_callback' => function ( $template_object ) { + if ( $template_object ) { + $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] ); + if ( $registered_template ) { + return $registered_template->plugin; + } + } + + return; + }, 'update_callback' => null, 'schema' => array( 'type' => 'string', From 6e11c6bce2e082f19ab1c19f7fa1ced897285d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 12:55:18 +0200 Subject: [PATCH 23/49] Remove support for registering templates with an object --- lib/class-wp-templates-registry.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 974cd935bb469d..04049733225127 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -34,19 +34,13 @@ final class WP_Templates_Registry { * * @since 6.7.0 * - * @param string|WP_Block_Template $template_name Template name including namespace, or alternatively - * a complete WP_Block_Template instance. In case a WP_Block_Template - * is provided, the $args parameter will be ignored. - * @param array $args Optional. Array of template arguments. + * @param string $template_name Template name including namespace. + * @param array $args Optional. Array of template arguments. * @return WP_Block_Template|false The registered template on success, or false on failure. */ public function register( $template_name, $args = array() ) { $template = null; - if ( $template_name instanceof WP_Block_Template ) { - $template = $template_name; - $template_name = $template->name; - } if ( ! is_string( $template_name ) ) { _doing_it_wrong( From 0a3e58f4e109ab4b603ba475b8703e7696c21d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 14:42:08 +0200 Subject: [PATCH 24/49] Refactor API so no prefix is needed and the slug prop is also unnecessary --- lib/class-wp-templates-registry.php | 62 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 04049733225127..5e4531cbb948c6 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -34,7 +34,7 @@ final class WP_Templates_Registry { * * @since 6.7.0 * - * @param string $template_name Template name including namespace. + * @param string $template_name Template name. * @param array $args Optional. Array of template arguments. * @return WP_Block_Template|false The registered template on success, or false on failure. */ @@ -60,39 +60,39 @@ public function register( $template_name, $args = array() ) { return new WP_Error( 'template_name_no_uppercase', __( 'Template names must not contain uppercase characters.', 'gutenberg' ) ); } - $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; - if ( ! preg_match( $name_matcher, $template_name ) ) { + if ( ! isset( $args['plugin' ] ) ) { _doing_it_wrong( __METHOD__, - __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), + __( 'Registered templates must have a plugin property.', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_no_prefix', __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ) ); + return new WP_Error( 'template_no_plugin', __( 'Registered templates must have a plugin property.', 'gutenberg' ) ); } - if ( $this->is_registered( $template_name ) ) { + $template_id = $args['plugin'] . '//' . $template_name; + + if ( $this->is_registered( $template_id ) ) { _doing_it_wrong( __METHOD__, - /* translators: %s: Template name. */ - sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ), + /* translators: %s: Template id. */ + sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_id ), '6.7.0' ); - /* translators: %s: Template name. */ + /* translators: %s: Template id. */ return new WP_Error( 'template_already_registered', __( 'Template "%s" is already registered.', 'gutenberg' ) ); } if ( ! $template ) { $theme_name = get_stylesheet(); - $slug = isset( $args['slug'] ) ? $args['slug'] : explode( '//', $template_name )[1]; $template = new WP_Block_Template(); - $template->id = $theme_name . '//' . $slug; + $template->id = $theme_name . '//' . $template_name; $template->theme = $theme_name; - $template->plugin = isset( $args['plugin'] ) ? $args['plugin'] : ''; + $template->plugin = $args['plugin']; $template->author = null; $template->content = isset( $args['content'] ) ? $args['content'] : ''; $template->source = 'plugin'; - $template->slug = $slug; + $template->slug = $template_name; $template->type = 'wp_template'; $template->title = isset( $args['title'] ) ? $args['title'] : ''; $template->description = isset( $args['description'] ) ? $args['description'] : ''; @@ -102,7 +102,7 @@ public function register( $template_name, $args = array() ) { $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; } - $this->registered_templates[ $template_name ] = $template; + $this->registered_templates[ $template_id ] = $template; return $template; } @@ -112,26 +112,26 @@ public function register( $template_name, $args = array() ) { * * @since 6.7.0 * - * @return WP_Block_Template[]|false Associative array of `$template_name => $template` pairs. + * @return WP_Block_Template[]|false Associative array of `$template_id => $template` pairs. */ public function get_all_registered() { return $this->registered_templates; } /** - * Retrieves a registered template by its and name. + * Retrieves a registered template by its id. * * @since 6.7.0 * - * @param string $template_name Template name including namespace. + * @param string $template_id Template id including namespace. * @return WP_Block_Template|null|false The registered template, or null if it is not registered. */ - public function get_registered( $template_name ) { - if ( ! $this->is_registered( $template_name ) ) { + public function get_registered( $template_id ) { + if ( ! $this->is_registered( $template_id ) ) { return null; } - return $this->registered_templates[ $template_name ]; + return $this->registered_templates[ $template_id ]; } /** @@ -191,7 +191,7 @@ public function get_by_query( $query = array() ) { $post_type = $query['post_type']; $matching_templates = array(); - foreach ( $all_templates as $template_name => $template ) { + foreach ( $all_templates as $template_id => $template ) { if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include, true ) ) { continue; } @@ -204,7 +204,7 @@ public function get_by_query( $query = array() ) { continue; } - $matching_templates[ $template_name ] = $template; + $matching_templates[ $template_id ] = $template; } return $matching_templates; @@ -215,11 +215,11 @@ public function get_by_query( $query = array() ) { * * @since 6.7.0 * - * @param string $template_name Template name including namespace. + * @param string $template_id Template id. * @return bool True if the template is registered, false otherwise. */ - public function is_registered( $template_name ) { - return isset( $this->registered_templates[ $template_name ] ); + public function is_registered( $template_id ) { + return isset( $this->registered_templates[ $template_id ] ); } /** @@ -227,23 +227,23 @@ public function is_registered( $template_name ) { * * @since 6.7.0 * - * @param string $name Template name including namespace. + * @param string $template_id Template id including namespace. * @return WP_Block_Template|false The unregistered template on success, or false on failure. */ - public function unregister( $template_name ) { - if ( ! $this->is_registered( $template_name ) ) { + public function unregister( $template_id ) { + if ( ! $this->is_registered( $template_id ) ) { _doing_it_wrong( __METHOD__, /* translators: %s: Template name. */ - sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ), + sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_id ), '6.7.0' ); /* translators: %s: Template name. */ return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) ); } - $unregistered_template = $this->registered_templates[ $template_name ]; - unset( $this->registered_templates[ $template_name ] ); + $unregistered_template = $this->registered_templates[ $template_id ]; + unset( $this->registered_templates[ $template_id ] ); return $unregistered_template; } From 94f1f2d7cb9995b400057f47c83db03567f9694e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 15:52:22 +0200 Subject: [PATCH 25/49] Correctly set is_custom prop --- lib/class-wp-templates-registry.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 5e4531cbb948c6..1df6282cb483df 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -83,7 +83,8 @@ public function register( $template_name, $args = array() ) { } if ( ! $template ) { - $theme_name = get_stylesheet(); + $theme_name = get_stylesheet(); + $default_template_types = get_default_block_template_types(); $template = new WP_Block_Template(); $template->id = $theme_name . '//' . $template_name; @@ -98,7 +99,7 @@ public function register( $template_name, $args = array() ) { $template->description = isset( $args['description'] ) ? $args['description'] : ''; $template->status = 'publish'; $template->origin = 'plugin'; - $template->is_custom = true; + $template->is_custom = ! isset( $default_template_types[ $template_name ] ); $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; } From 4972b257853cc29547af4b3f69c3971137c877fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 15:52:41 +0200 Subject: [PATCH 26/49] Make sure plugin.php has been intitialized when trying to access plugins --- .../class-gutenberg-rest-templates-controller-6-7.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 2c58de995156e5..d84748bc124635 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -182,6 +182,9 @@ private static function get_wp_templates_author_text_field( $template_object ) { * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards * compatibility with templates that were registered before the plugin attribute was added. */ + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } $plugins = get_plugins(); $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { From 231a82b9fcde1804b4529425a0b22e48b6b8ac9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 17:00:46 +0200 Subject: [PATCH 27/49] Add docblock to gutenberg_register_template() and create gutenberg_unregister_template() function --- lib/block-templates.php | 28 ++++++++++++++++++++++++++++ lib/class-wp-templates-registry.php | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/block-templates.php b/lib/block-templates.php index 1f507d4d054899..1383bc260fd1b8 100644 --- a/lib/block-templates.php +++ b/lib/block-templates.php @@ -5,6 +5,34 @@ * @package gutenberg */ +/** + * Register a template. + * + * @param string $template_name Template name. It can only contain lowercase alphanumeric characters, dashes, + * and underscores. See sanitize_key(). + * @param array|string $args Object type or array of object types with which the taxonomy should be associated. + * @param array|string $args { + * @type string $title Optional. Title of the template as it will be shown in the Site Editor + * and other UI elements. + * @type string $description Optional. Description of the template as it will be shown in the Site + * Editor. + * @type string $content Optional. Default content of the template that will be used when the + * template is rendered or edited in the editor. + * @type string[] $post_types Optional. Array of post types to which the template should be available. + * @type string $plugin Uri of the plugin that registers the template. + * } + * @return WP_Block_Template|WP_Error The registered template object on success, WP_Error object on failure. + */ function gutenberg_register_template( $template_name, $args = array() ) { return WP_Templates_Registry::get_instance()->register( $template_name, $args ); } + +/** + * Unregister a template. + * + * @param string $template_id Template id in the form of `plugin_uri//template_name`. + * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist. + */ +function gutenberg_unregister_template( $template_id ) { + return WP_Templates_Registry::get_instance()->unregister( $template_id ); +} diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 1df6282cb483df..8cc565c23c754c 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -95,7 +95,7 @@ public function register( $template_name, $args = array() ) { $template->source = 'plugin'; $template->slug = $template_name; $template->type = 'wp_template'; - $template->title = isset( $args['title'] ) ? $args['title'] : ''; + $template->title = isset( $args['title'] ) ? $args['title'] : $template_name; $template->description = isset( $args['description'] ) ? $args['description'] : ''; $template->status = 'publish'; $template->origin = 'plugin'; From 1082b146c091417f78c5562e77119049b25073aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 17:03:13 +0200 Subject: [PATCH 28/49] Linting --- lib/class-wp-templates-registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 8cc565c23c754c..9893bcddca3eb6 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -60,7 +60,7 @@ public function register( $template_name, $args = array() ) { return new WP_Error( 'template_name_no_uppercase', __( 'Template names must not contain uppercase characters.', 'gutenberg' ) ); } - if ( ! isset( $args['plugin' ] ) ) { + if ( ! isset( $args['plugin'] ) ) { _doing_it_wrong( __METHOD__, __( 'Registered templates must have a plugin property.', 'gutenberg' ), From 3c4d9ba6958bc03406c5e0b121d6f5f6e35e3953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 17:08:12 +0200 Subject: [PATCH 29/49] Fix typo --- lib/class-wp-templates-registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 9893bcddca3eb6..46a0efed5b9f3c 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -201,7 +201,7 @@ public function get_by_query( $query = array() ) { continue; } - if ( ! empty( $post_type ) && ! in_array( $post_type, $template->post_types, true ) ) { + if ( ! empty( $post_types ) && ! in_array( $post_type, $template->post_types, true ) ) { continue; } From c021b3fc4b5e0bf8bf9b2e8d8dc1293af5bb50e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 17 Jul 2024 17:11:07 +0200 Subject: [PATCH 30/49] Make sure plugin.php has been intitialized when trying to access plugins (II) --- .../class-gutenberg-rest-templates-controller-6-7.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index d84748bc124635..d75f37ae06ffdf 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -158,6 +158,9 @@ private static function get_wp_templates_author_text_field( $template_object ) { return empty( $theme_name ) ? $template_object->theme : $theme_name; case 'plugin': // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates. + if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'get_plugin_data' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } if ( isset( $template_object->plugin ) ) { $plugins = wp_get_active_and_valid_plugins(); @@ -182,9 +185,6 @@ private static function get_wp_templates_author_text_field( $template_object ) { * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards * compatibility with templates that were registered before the plugin attribute was added. */ - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } $plugins = get_plugins(); $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { From 1f8274bbd8ef084635ce1243330157f641f7efd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 18 Jul 2024 14:33:56 +0200 Subject: [PATCH 31/49] Fix wrong 'origin' set in plugin templates that were customized by the user --- lib/compat/wordpress-6.7/compat.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index cdc536c2157cb1..218f95b5df0568 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -28,6 +28,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); if ( $registered_template ) { $query_result[ $key ]->plugin = $registered_template->plugin; + $query_result[ $key ]->origin = 'theme' !== $query_result[ $key ]->origin ? 'plugin' : $query_result[ $key ]->origin; } } @@ -67,6 +68,7 @@ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; + $block_template->origin = 'theme' !== $block_template->origin ? 'plugin' : $block_template->origin; } } @@ -86,6 +88,7 @@ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; + $block_template->origin = 'theme' !== $block_template->origin ? 'plugin' : $block_template->origin; } return $block_template; } From 469e68052fce4250a7343496d9f8dd7e0f7cddeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 24 Jul 2024 17:58:42 +0200 Subject: [PATCH 32/49] Add PHP unit tests --- lib/class-wp-templates-registry.php | 2 +- phpunit/block-template-test.php | 41 ++++ ...tenberg-rest-templates-controller-test.php | 119 ++++++++++ phpunit/class-wp-templates-registry-test.php | 215 ++++++++++++++++++ 4 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 phpunit/block-template-test.php create mode 100644 phpunit/class-gutenberg-rest-templates-controller-test.php create mode 100644 phpunit/class-wp-templates-registry-test.php diff --git a/lib/class-wp-templates-registry.php b/lib/class-wp-templates-registry.php index 46a0efed5b9f3c..16772e303dd11e 100644 --- a/lib/class-wp-templates-registry.php +++ b/lib/class-wp-templates-registry.php @@ -79,7 +79,7 @@ public function register( $template_name, $args = array() ) { '6.7.0' ); /* translators: %s: Template id. */ - return new WP_Error( 'template_already_registered', __( 'Template "%s" is already registered.', 'gutenberg' ) ); + return new WP_Error( 'template_already_registered', sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_id ) ); } if ( ! $template ) { diff --git a/phpunit/block-template-test.php b/phpunit/block-template-test.php new file mode 100644 index 00000000000000..b49364522a0eac --- /dev/null +++ b/phpunit/block-template-test.php @@ -0,0 +1,41 @@ + 'test-plugin', + ); + + gutenberg_register_template( $template_name, $args ); + + $templates = get_block_templates(); + + $this->assertArrayHasKey( 'test-plugin//test-template', $templates ); + + gutenberg_unregister_template( 'test-plugin//' . $template_name ); + } + + public function test_get_block_template_from_registry() { + $template_name = 'test-template'; + $args = array( + 'plugin' => 'test-plugin', + 'title' => 'Test Template', + ); + + gutenberg_register_template( $template_name, $args ); + + $template = get_block_template( 'block-theme//test-template' ); + + $this->assertEquals( 'Test Template', $template->title ); + + gutenberg_unregister_template( 'test-plugin//' . $template_name ); + } +} diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php new file mode 100644 index 00000000000000..340faeaab0b54f --- /dev/null +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -0,0 +1,119 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + } + + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + + $template_name = 'test-template'; + $args = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content', + 'title' => 'Test Template', + 'description' => 'Description of test template', + 'post_types' => array( 'post', 'page' ), + ); + + gutenberg_register_template( $template_name, $args ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertNotWPError( $response ); + + $data = $response->get_data(); + + $this->assertEquals( 'default//test-template', $data['id'] ); + $this->assertEquals( 'default', $data['theme'] ); + $this->assertEquals( 'Template content', $data['content']['raw'] ); + $this->assertEquals( 'test-template', $data['slug'] ); + $this->assertEquals( 'plugin', $data['source'] ); + $this->assertEquals( 'plugin', $data['origin'] ); + $this->assertEquals( 'Description of test template', $data['description'] ); + $this->assertEquals( 'Test Template', $data['title']['rendered'] ); + $this->assertEquals( 'test-plugin', $data['plugin'] ); + + gutenberg_unregister_template( 'test-plugin//' . $template_name ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertNotWPError( $response ); + $this->assertEquals( 404, $response->get_status() ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_register_routes() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_get_items() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_create_item() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_update_item() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_delete_item() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_prepare_item() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } + + /** + * @doesNotPerformAssertions + */ + public function test_get_item_schema() { + // Already present in core test class: Tests_REST_WpRestTemplatesController. + } +} diff --git a/phpunit/class-wp-templates-registry-test.php b/phpunit/class-wp-templates-registry-test.php new file mode 100644 index 00000000000000..e8bb0fff831ee9 --- /dev/null +++ b/phpunit/class-wp-templates-registry-test.php @@ -0,0 +1,215 @@ + 'test-plugin', + ); + $template = self::$registry->register( $template_name, $args ); + + $this->assertEquals( $template->slug, $template_name ); + + self::$registry->unregister( 'test-plugin//' . $template_name ); + } + + public function test_register_template_invalid_name() { + // Try to register a template with invalid name (non-string). + $template_name = array( 'invalid-template-name' ); + $args = array( + 'plugin' => 'test-plugin', + ); + + $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $result = self::$registry->register( $template_name, $args ); + + $this->assertWPError( $result ); + $this->assertEquals( 'template_name_no_string', $result->get_error_code() ); + $this->assertEquals( 'Template names must be a string.', $result->get_error_message() ); + } + + public function test_register_template_invalid_name_uppercase() { + // Try to register a template with uppercase characters in the name. + $template_name = 'Invalid-Template-Name'; + $args = array( + 'plugin' => 'test-plugin', + ); + + $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $result = self::$registry->register( $template_name, $args ); + + $this->assertWPError( $result ); + $this->assertEquals( 'template_name_no_uppercase', $result->get_error_code() ); + $this->assertEquals( 'Template names must not contain uppercase characters.', $result->get_error_message() ); + } + + public function test_register_template_no_plugin_property() { + // Try to register a template without a plugin property. + $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $result = self::$registry->register( 'template-no-plugin', array() ); + + $this->assertWPError( $result ); + $this->assertEquals( 'template_no_plugin', $result->get_error_code() ); + $this->assertEquals( 'Registered templates must have a plugin property.', $result->get_error_message() ); + } + + public function test_register_template_already_exists() { + // Register the template for the first time. + $template_name = 'duplicate-template'; + $args = array( + 'plugin' => 'test-plugin', + ); + self::$registry->register( $template_name, $args ); + + // Try to register the same template again. + $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $result = self::$registry->register( $template_name, $args ); + + $this->assertWPError( $result ); + $this->assertEquals( 'template_already_registered', $result->get_error_code() ); + $this->assertStringContainsString( 'Template "test-plugin//duplicate-template" is already registered.', $result->get_error_message() ); + + self::$registry->unregister( 'test-plugin//' . $template_name ); + } + + public function test_get_all_registered() { + $template_name_1 = 'template-1'; + $template_name_2 = 'template-2'; + $args = array( + 'plugin' => 'test-plugin', + ); + self::$registry->register( $template_name_1, $args ); + self::$registry->register( $template_name_2, $args ); + + $all_templates = self::$registry->get_all_registered(); + + $this->assertIsArray( $all_templates ); + $this->assertCount( 2, $all_templates ); + $this->assertArrayHasKey( 'test-plugin//template-1', $all_templates ); + $this->assertArrayHasKey( 'test-plugin//template-2', $all_templates ); + + self::$registry->unregister( 'test-plugin//' . $template_name_1 ); + self::$registry->unregister( 'test-plugin//' . $template_name_2 ); + } + + public function test_get_registered() { + $template_name = 'registered-template'; + $args = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content', + 'title' => 'Registered Template', + 'description' => 'Description of registered template', + 'post_types' => array( 'post', 'page' ), + ); + self::$registry->register( $template_name, $args ); + $template_id = $args['plugin'] . '//' . $template_name; + + $registered_template = self::$registry->get_registered( $template_id ); + + $this->assertEquals( 'default', $registered_template->theme ); + $this->assertEquals( 'registered-template', $registered_template->slug ); + $this->assertEquals( 'default//registered-template', $registered_template->id ); + $this->assertEquals( 'Registered Template', $registered_template->title ); + $this->assertEquals( 'Template content', $registered_template->content ); + $this->assertEquals( 'Description of registered template', $registered_template->description ); + $this->assertEquals( 'plugin', $registered_template->source ); + $this->assertEquals( 'plugin', $registered_template->origin ); + $this->assertEquals( array( 'post', 'page' ), $registered_template->post_types ); + $this->assertEquals( 'test-plugin', $registered_template->plugin ); + + self::$registry->unregister( $template_id ); + } + + public function test_get_by_slug() { + $template_name = 'slug-template'; + $args = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content', + 'title' => 'Slug Template', + ); + self::$registry->register( $template_name, $args ); + + $registered_template = self::$registry->get_by_slug( $template_name ); + + $this->assertNotNull( $registered_template ); + $this->assertEquals( $template_name, $registered_template->slug ); + + self::$registry->unregister( 'test-plugin//' . $template_name ); + } + + public function test_get_by_query() { + $template_name_1 = 'query-template-1'; + $template_name_2 = 'query-template-2'; + $args_1 = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content 1', + 'title' => 'Query Template 1', + ); + $args_2 = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content 2', + 'title' => 'Query Template 2', + ); + self::$registry->register( $template_name_1, $args_1 ); + self::$registry->register( $template_name_2, $args_2 ); + + $query = array( + 'slug__in' => array( 'query-template-1' ), + ); + $results = self::$registry->get_by_query( $query ); + + $this->assertCount( 1, $results ); + $this->assertArrayHasKey( 'test-plugin//query-template-1', $results ); + + self::$registry->unregister( 'test-plugin//' . $template_name_1 ); + self::$registry->unregister( 'test-plugin//' . $template_name_2 ); + } + + public function test_is_registered() { + $template_name = 'is-registered-template'; + $args = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content', + 'title' => 'Is Registered Template', + ); + self::$registry->register( $template_name, $args ); + $template_id = $args['plugin'] . '//' . $template_name; + + $this->assertTrue( self::$registry->is_registered( $template_id ) ); + + self::$registry->unregister( $template_id ); + } + + public function test_unregister() { + $template_name = 'unregister-template'; + $args = array( + 'plugin' => 'test-plugin', + 'content' => 'Template content', + 'title' => 'Unregister Template', + ); + $template = self::$registry->register( $template_name, $args ); + $template_id = $args['plugin'] . '//' . $template_name; + + $unregistered_template = self::$registry->unregister( $template_id ); + + $this->assertEquals( $template, $unregistered_template ); + $this->assertFalse( self::$registry->is_registered( $template_id ) ); + } +} From 44ce8986409f6a14e55d9a512e755eb01797d67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Fri, 26 Jul 2024 12:31:55 +0200 Subject: [PATCH 33/49] Move files under WP 6.7 folder and rename API functions from gutenberg_ to wp_ --- lib/block-templates.php | 38 ----------------- lib/compat/wordpress-6.7/block-templates.php | 42 +++++++++++++++++++ .../class-wp-templates-registry.php | 0 lib/load.php | 4 +- phpunit/block-template-test.php | 8 ++-- ...tenberg-rest-templates-controller-test.php | 4 +- 6 files changed, 50 insertions(+), 46 deletions(-) delete mode 100644 lib/block-templates.php create mode 100644 lib/compat/wordpress-6.7/block-templates.php rename lib/{ => compat/wordpress-6.7}/class-wp-templates-registry.php (100%) diff --git a/lib/block-templates.php b/lib/block-templates.php deleted file mode 100644 index 1383bc260fd1b8..00000000000000 --- a/lib/block-templates.php +++ /dev/null @@ -1,38 +0,0 @@ -register( $template_name, $args ); -} - -/** - * Unregister a template. - * - * @param string $template_id Template id in the form of `plugin_uri//template_name`. - * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist. - */ -function gutenberg_unregister_template( $template_id ) { - return WP_Templates_Registry::get_instance()->unregister( $template_id ); -} diff --git a/lib/compat/wordpress-6.7/block-templates.php b/lib/compat/wordpress-6.7/block-templates.php new file mode 100644 index 00000000000000..d3e1dab3abfc59 --- /dev/null +++ b/lib/compat/wordpress-6.7/block-templates.php @@ -0,0 +1,42 @@ +register( $template_name, $args ); + } +} + +if ( ! function_exists( 'wp_unregister_template' ) ) { + /** + * Unregister a template. + * + * @param string $template_id Template id in the form of `plugin_uri//template_name`. + * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist. + */ + function wp_unregister_template( $template_id ) { + return WP_Templates_Registry::get_instance()->unregister( $template_id ); + } +} diff --git a/lib/class-wp-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-templates-registry.php similarity index 100% rename from lib/class-wp-templates-registry.php rename to lib/compat/wordpress-6.7/class-wp-templates-registry.php diff --git a/lib/load.php b/lib/load.php index 4c429064054d1b..16c81d272661df 100644 --- a/lib/load.php +++ b/lib/load.php @@ -102,9 +102,11 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.6/post.php'; // WordPress 6.7 compat. +require __DIR__ . '/compat/wordpress-6.7/block-templates.php'; require __DIR__ . '/compat/wordpress-6.7/blocks.php'; require __DIR__ . '/compat/wordpress-6.7/block-bindings.php'; require __DIR__ . '/compat/wordpress-6.7/script-modules.php'; +require __DIR__ . '/compat/wordpress-6.7/class-wp-templates-registry.php'; require __DIR__ . '/compat/wordpress-6.7/compat.php'; // Experimental features. @@ -144,9 +146,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/class-wp-theme-json-resolver-gutenberg.php'; require __DIR__ . '/class-wp-theme-json-schema-gutenberg.php'; require __DIR__ . '/class-wp-duotone-gutenberg.php'; -require __DIR__ . '/class-wp-templates-registry.php'; require __DIR__ . '/blocks.php'; -require __DIR__ . '/block-templates.php'; require __DIR__ . '/block-editor-settings.php'; require __DIR__ . '/client-assets.php'; require __DIR__ . '/demo.php'; diff --git a/phpunit/block-template-test.php b/phpunit/block-template-test.php index b49364522a0eac..0a0edf13e7acb2 100644 --- a/phpunit/block-template-test.php +++ b/phpunit/block-template-test.php @@ -14,13 +14,13 @@ public function test_get_block_templates_from_registry() { 'plugin' => 'test-plugin', ); - gutenberg_register_template( $template_name, $args ); + wp_register_template( $template_name, $args ); $templates = get_block_templates(); $this->assertArrayHasKey( 'test-plugin//test-template', $templates ); - gutenberg_unregister_template( 'test-plugin//' . $template_name ); + wp_unregister_template( 'test-plugin//' . $template_name ); } public function test_get_block_template_from_registry() { @@ -30,12 +30,12 @@ public function test_get_block_template_from_registry() { 'title' => 'Test Template', ); - gutenberg_register_template( $template_name, $args ); + wp_register_template( $template_name, $args ); $template = get_block_template( 'block-theme//test-template' ); $this->assertEquals( 'Test Template', $template->title ); - gutenberg_unregister_template( 'test-plugin//' . $template_name ); + wp_unregister_template( 'test-plugin//' . $template_name ); } } diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index 340faeaab0b54f..c6088af487f41d 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -33,7 +33,7 @@ public function test_get_item() { 'post_types' => array( 'post', 'page' ), ); - gutenberg_register_template( $template_name, $args ); + wp_register_template( $template_name, $args ); $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); @@ -52,7 +52,7 @@ public function test_get_item() { $this->assertEquals( 'Test Template', $data['title']['rendered'] ); $this->assertEquals( 'test-plugin', $data['plugin'] ); - gutenberg_unregister_template( 'test-plugin//' . $template_name ); + wp_unregister_template( 'test-plugin//' . $template_name ); $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); From ae53a3af2a58e8664adffc2a2858e94bc733ed48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Fri, 26 Jul 2024 16:22:59 +0200 Subject: [PATCH 34/49] Add back template namespace and remove plugin property --- lib/compat/wordpress-6.7/block-templates.php | 9 +- .../class-wp-templates-registry.php | 64 +++++------ phpunit/block-template-test.php | 18 ++- ...tenberg-rest-templates-controller-test.php | 5 +- phpunit/class-wp-templates-registry-test.php | 103 +++++++----------- 5 files changed, 85 insertions(+), 114 deletions(-) diff --git a/lib/compat/wordpress-6.7/block-templates.php b/lib/compat/wordpress-6.7/block-templates.php index d3e1dab3abfc59..b9518b681f75c4 100644 --- a/lib/compat/wordpress-6.7/block-templates.php +++ b/lib/compat/wordpress-6.7/block-templates.php @@ -9,8 +9,7 @@ /** * Register a template. * - * @param string $template_name Template name. It can only contain lowercase alphanumeric characters, dashes, - * and underscores. See sanitize_key(). + * @param string $template_name Template name in the form of `plugin_uri//template_name`. * @param array|string $args Object type or array of object types with which the taxonomy should be associated. * @param array|string $args { * @type string $title Optional. Title of the template as it will be shown in the Site Editor @@ -33,10 +32,10 @@ function wp_register_template( $template_name, $args = array() ) { /** * Unregister a template. * - * @param string $template_id Template id in the form of `plugin_uri//template_name`. + * @param string $template_name Template name in the form of `plugin_uri//template_name`. * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist. */ - function wp_unregister_template( $template_id ) { - return WP_Templates_Registry::get_instance()->unregister( $template_id ); + function wp_unregister_template( $template_name ) { + return WP_Templates_Registry::get_instance()->unregister( $template_name ); } } diff --git a/lib/compat/wordpress-6.7/class-wp-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-templates-registry.php index 16772e303dd11e..8fd25e5439a11d 100644 --- a/lib/compat/wordpress-6.7/class-wp-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-templates-registry.php @@ -34,7 +34,7 @@ final class WP_Templates_Registry { * * @since 6.7.0 * - * @param string $template_name Template name. + * @param string $template_name Template name including namespace. * @param array $args Optional. Array of template arguments. * @return WP_Block_Template|false The registered template on success, or false on failure. */ @@ -60,40 +60,40 @@ public function register( $template_name, $args = array() ) { return new WP_Error( 'template_name_no_uppercase', __( 'Template names must not contain uppercase characters.', 'gutenberg' ) ); } - if ( ! isset( $args['plugin'] ) ) { + $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; + if ( ! preg_match( $name_matcher, $template_name ) ) { _doing_it_wrong( __METHOD__, - __( 'Registered templates must have a plugin property.', 'gutenberg' ), + __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), '6.7.0' ); - return new WP_Error( 'template_no_plugin', __( 'Registered templates must have a plugin property.', 'gutenberg' ) ); + return new WP_Error( 'template_no_prefix', __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ) ); } - $template_id = $args['plugin'] . '//' . $template_name; - - if ( $this->is_registered( $template_id ) ) { + if ( $this->is_registered( $template_name ) ) { _doing_it_wrong( __METHOD__, - /* translators: %s: Template id. */ - sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_id ), + /* translators: %s: Template name. */ + sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ), '6.7.0' ); - /* translators: %s: Template id. */ - return new WP_Error( 'template_already_registered', sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_id ) ); + /* translators: %s: Template name. */ + return new WP_Error( 'template_already_registered', sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ) ); } if ( ! $template ) { $theme_name = get_stylesheet(); + list( $plugin, $slug ) = explode( '//', $template_name ); $default_template_types = get_default_block_template_types(); $template = new WP_Block_Template(); - $template->id = $theme_name . '//' . $template_name; + $template->id = $theme_name . '//' . $slug; $template->theme = $theme_name; - $template->plugin = $args['plugin']; + $template->plugin = $plugin; $template->author = null; $template->content = isset( $args['content'] ) ? $args['content'] : ''; $template->source = 'plugin'; - $template->slug = $template_name; + $template->slug = $slug; $template->type = 'wp_template'; $template->title = isset( $args['title'] ) ? $args['title'] : $template_name; $template->description = isset( $args['description'] ) ? $args['description'] : ''; @@ -103,7 +103,7 @@ public function register( $template_name, $args = array() ) { $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; } - $this->registered_templates[ $template_id ] = $template; + $this->registered_templates[ $template_name ] = $template; return $template; } @@ -113,26 +113,26 @@ public function register( $template_name, $args = array() ) { * * @since 6.7.0 * - * @return WP_Block_Template[]|false Associative array of `$template_id => $template` pairs. + * @return WP_Block_Template[]|false Associative array of `$template_name => $template` pairs. */ public function get_all_registered() { return $this->registered_templates; } /** - * Retrieves a registered template by its id. + * Retrieves a registered template by its name. * * @since 6.7.0 * - * @param string $template_id Template id including namespace. + * @param string $template_name Template name including namespace. * @return WP_Block_Template|null|false The registered template, or null if it is not registered. */ - public function get_registered( $template_id ) { - if ( ! $this->is_registered( $template_id ) ) { + public function get_registered( $template_name ) { + if ( ! $this->is_registered( $template_name ) ) { return null; } - return $this->registered_templates[ $template_id ]; + return $this->registered_templates[ $template_name ]; } /** @@ -192,7 +192,7 @@ public function get_by_query( $query = array() ) { $post_type = $query['post_type']; $matching_templates = array(); - foreach ( $all_templates as $template_id => $template ) { + foreach ( $all_templates as $template_name => $template ) { if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include, true ) ) { continue; } @@ -205,7 +205,7 @@ public function get_by_query( $query = array() ) { continue; } - $matching_templates[ $template_id ] = $template; + $matching_templates[ $template_name ] = $template; } return $matching_templates; @@ -216,11 +216,11 @@ public function get_by_query( $query = array() ) { * * @since 6.7.0 * - * @param string $template_id Template id. + * @param string $template_name Template name. * @return bool True if the template is registered, false otherwise. */ - public function is_registered( $template_id ) { - return isset( $this->registered_templates[ $template_id ] ); + public function is_registered( $template_name ) { + return isset( $this->registered_templates[ $template_name ] ); } /** @@ -228,23 +228,23 @@ public function is_registered( $template_id ) { * * @since 6.7.0 * - * @param string $template_id Template id including namespace. + * @param string $template_name Template name including namespace. * @return WP_Block_Template|false The unregistered template on success, or false on failure. */ - public function unregister( $template_id ) { - if ( ! $this->is_registered( $template_id ) ) { + public function unregister( $template_name ) { + if ( ! $this->is_registered( $template_name ) ) { _doing_it_wrong( __METHOD__, /* translators: %s: Template name. */ - sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_id ), + sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ), '6.7.0' ); /* translators: %s: Template name. */ return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) ); } - $unregistered_template = $this->registered_templates[ $template_id ]; - unset( $this->registered_templates[ $template_id ] ); + $unregistered_template = $this->registered_templates[ $template_name ]; + unset( $this->registered_templates[ $template_name ] ); return $unregistered_template; } diff --git a/phpunit/block-template-test.php b/phpunit/block-template-test.php index 0a0edf13e7acb2..c1a4ed78188491 100644 --- a/phpunit/block-template-test.php +++ b/phpunit/block-template-test.php @@ -9,25 +9,21 @@ public function set_up() { } public function test_get_block_templates_from_registry() { - $template_name = 'test-template'; - $args = array( - 'plugin' => 'test-plugin', - ); + $template_name = 'test-plugin//test-template'; - wp_register_template( $template_name, $args ); + wp_register_template( $template_name ); $templates = get_block_templates(); - $this->assertArrayHasKey( 'test-plugin//test-template', $templates ); + $this->assertArrayHasKey( $template_name, $templates ); - wp_unregister_template( 'test-plugin//' . $template_name ); + wp_unregister_template( $template_name ); } public function test_get_block_template_from_registry() { - $template_name = 'test-template'; + $template_name = 'test-plugin//test-template'; $args = array( - 'plugin' => 'test-plugin', - 'title' => 'Test Template', + 'title' => 'Test Template', ); wp_register_template( $template_name, $args ); @@ -36,6 +32,6 @@ public function test_get_block_template_from_registry() { $this->assertEquals( 'Test Template', $template->title ); - wp_unregister_template( 'test-plugin//' . $template_name ); + wp_unregister_template( $template_name ); } } diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index c6088af487f41d..af87b6f9c4e4a7 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -24,9 +24,8 @@ public static function wpTearDownAfterClass() { public function test_get_item() { wp_set_current_user( self::$admin_id ); - $template_name = 'test-template'; + $template_name = 'test-plugin//test-template'; $args = array( - 'plugin' => 'test-plugin', 'content' => 'Template content', 'title' => 'Test Template', 'description' => 'Description of test template', @@ -52,7 +51,7 @@ public function test_get_item() { $this->assertEquals( 'Test Template', $data['title']['rendered'] ); $this->assertEquals( 'test-plugin', $data['plugin'] ); - wp_unregister_template( 'test-plugin//' . $template_name ); + wp_unregister_template( $template_name ); $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); diff --git a/phpunit/class-wp-templates-registry-test.php b/phpunit/class-wp-templates-registry-test.php index e8bb0fff831ee9..5c0a7201164f62 100644 --- a/phpunit/class-wp-templates-registry-test.php +++ b/phpunit/class-wp-templates-registry-test.php @@ -19,26 +19,20 @@ public static function set_up_before_class() { public function test_register_template() { // Register a valid template. - $template_name = 'test-template'; - $args = array( - 'plugin' => 'test-plugin', - ); - $template = self::$registry->register( $template_name, $args ); + $template_name = 'test-plugin//test-template'; + $template = self::$registry->register( $template_name ); - $this->assertEquals( $template->slug, $template_name ); + $this->assertEquals( $template->slug, 'test-template' ); - self::$registry->unregister( 'test-plugin//' . $template_name ); + self::$registry->unregister( $template_name ); } public function test_register_template_invalid_name() { // Try to register a template with invalid name (non-string). $template_name = array( 'invalid-template-name' ); - $args = array( - 'plugin' => 'test-plugin', - ); $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); - $result = self::$registry->register( $template_name, $args ); + $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); $this->assertEquals( 'template_name_no_string', $result->get_error_code() ); @@ -47,56 +41,47 @@ public function test_register_template_invalid_name() { public function test_register_template_invalid_name_uppercase() { // Try to register a template with uppercase characters in the name. - $template_name = 'Invalid-Template-Name'; - $args = array( - 'plugin' => 'test-plugin', - ); + $template_name = 'test-plugin//Invalid-Template-Name'; $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); - $result = self::$registry->register( $template_name, $args ); + $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); $this->assertEquals( 'template_name_no_uppercase', $result->get_error_code() ); $this->assertEquals( 'Template names must not contain uppercase characters.', $result->get_error_message() ); } - public function test_register_template_no_plugin_property() { - // Try to register a template without a plugin property. + public function test_register_template_no_prefix() { + // Try to register a template without a namespace. $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); $result = self::$registry->register( 'template-no-plugin', array() ); $this->assertWPError( $result ); - $this->assertEquals( 'template_no_plugin', $result->get_error_code() ); - $this->assertEquals( 'Registered templates must have a plugin property.', $result->get_error_message() ); + $this->assertEquals( 'template_no_prefix', $result->get_error_code() ); + $this->assertEquals( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', $result->get_error_message() ); } public function test_register_template_already_exists() { // Register the template for the first time. - $template_name = 'duplicate-template'; - $args = array( - 'plugin' => 'test-plugin', - ); - self::$registry->register( $template_name, $args ); + $template_name = 'test-plugin//duplicate-template'; + self::$registry->register( $template_name ); // Try to register the same template again. $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); - $result = self::$registry->register( $template_name, $args ); + $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); $this->assertEquals( 'template_already_registered', $result->get_error_code() ); $this->assertStringContainsString( 'Template "test-plugin//duplicate-template" is already registered.', $result->get_error_message() ); - self::$registry->unregister( 'test-plugin//' . $template_name ); + self::$registry->unregister( $template_name ); } public function test_get_all_registered() { - $template_name_1 = 'template-1'; - $template_name_2 = 'template-2'; - $args = array( - 'plugin' => 'test-plugin', - ); - self::$registry->register( $template_name_1, $args ); - self::$registry->register( $template_name_2, $args ); + $template_name_1 = 'test-plugin//template-1'; + $template_name_2 = 'test-plugin//template-2'; + self::$registry->register( $template_name_1 ); + self::$registry->register( $template_name_2 ); $all_templates = self::$registry->get_all_registered(); @@ -105,23 +90,21 @@ public function test_get_all_registered() { $this->assertArrayHasKey( 'test-plugin//template-1', $all_templates ); $this->assertArrayHasKey( 'test-plugin//template-2', $all_templates ); - self::$registry->unregister( 'test-plugin//' . $template_name_1 ); - self::$registry->unregister( 'test-plugin//' . $template_name_2 ); + self::$registry->unregister( $template_name_1 ); + self::$registry->unregister( $template_name_2 ); } public function test_get_registered() { - $template_name = 'registered-template'; + $template_name = 'test-plugin//registered-template'; $args = array( - 'plugin' => 'test-plugin', 'content' => 'Template content', 'title' => 'Registered Template', 'description' => 'Description of registered template', 'post_types' => array( 'post', 'page' ), ); self::$registry->register( $template_name, $args ); - $template_id = $args['plugin'] . '//' . $template_name; - $registered_template = self::$registry->get_registered( $template_id ); + $registered_template = self::$registry->get_registered( $template_name ); $this->assertEquals( 'default', $registered_template->theme ); $this->assertEquals( 'registered-template', $registered_template->slug ); @@ -134,36 +117,34 @@ public function test_get_registered() { $this->assertEquals( array( 'post', 'page' ), $registered_template->post_types ); $this->assertEquals( 'test-plugin', $registered_template->plugin ); - self::$registry->unregister( $template_id ); + self::$registry->unregister( $template_name ); } public function test_get_by_slug() { - $template_name = 'slug-template'; + $slug = 'slug-template'; + $template_name = 'test-plugin//' . $slug; $args = array( - 'plugin' => 'test-plugin', 'content' => 'Template content', 'title' => 'Slug Template', ); self::$registry->register( $template_name, $args ); - $registered_template = self::$registry->get_by_slug( $template_name ); + $registered_template = self::$registry->get_by_slug( $slug ); $this->assertNotNull( $registered_template ); - $this->assertEquals( $template_name, $registered_template->slug ); + $this->assertEquals( $slug, $registered_template->slug ); - self::$registry->unregister( 'test-plugin//' . $template_name ); + self::$registry->unregister( $template_name ); } public function test_get_by_query() { - $template_name_1 = 'query-template-1'; - $template_name_2 = 'query-template-2'; + $template_name_1 = 'test-plugin//query-template-1'; + $template_name_2 = 'test-plugin//query-template-2'; $args_1 = array( - 'plugin' => 'test-plugin', 'content' => 'Template content 1', 'title' => 'Query Template 1', ); $args_2 = array( - 'plugin' => 'test-plugin', 'content' => 'Template content 2', 'title' => 'Query Template 2', ); @@ -176,40 +157,36 @@ public function test_get_by_query() { $results = self::$registry->get_by_query( $query ); $this->assertCount( 1, $results ); - $this->assertArrayHasKey( 'test-plugin//query-template-1', $results ); + $this->assertArrayHasKey( $template_name_1, $results ); - self::$registry->unregister( 'test-plugin//' . $template_name_1 ); - self::$registry->unregister( 'test-plugin//' . $template_name_2 ); + self::$registry->unregister( $template_name_1 ); + self::$registry->unregister( $template_name_2 ); } public function test_is_registered() { - $template_name = 'is-registered-template'; + $template_name = 'test-plugin//is-registered-template'; $args = array( - 'plugin' => 'test-plugin', 'content' => 'Template content', 'title' => 'Is Registered Template', ); self::$registry->register( $template_name, $args ); - $template_id = $args['plugin'] . '//' . $template_name; - $this->assertTrue( self::$registry->is_registered( $template_id ) ); + $this->assertTrue( self::$registry->is_registered( $template_name ) ); - self::$registry->unregister( $template_id ); + self::$registry->unregister( $template_name ); } public function test_unregister() { - $template_name = 'unregister-template'; + $template_name = 'test-plugin//unregister-template'; $args = array( - 'plugin' => 'test-plugin', 'content' => 'Template content', 'title' => 'Unregister Template', ); $template = self::$registry->register( $template_name, $args ); - $template_id = $args['plugin'] . '//' . $template_name; - $unregistered_template = self::$registry->unregister( $template_id ); + $unregistered_template = self::$registry->unregister( $template_name ); $this->assertEquals( $template, $unregistered_template ); - $this->assertFalse( self::$registry->is_registered( $template_id ) ); + $this->assertFalse( self::$registry->is_registered( $template_name ) ); } } From e8fb1ea13f4e38fd18174f59fc50698bd57f74e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Mon, 29 Jul 2024 15:23:18 +0200 Subject: [PATCH 35/49] Add e2e tests --- .../class-wp-templates-registry.php | 2 +- .../plugins/template-registration.php | 72 ++++ .../site-editor/template-registration.spec.js | 345 ++++++++++++++++++ 3 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 packages/e2e-tests/plugins/template-registration.php create mode 100644 test/e2e/specs/site-editor/template-registration.spec.js diff --git a/lib/compat/wordpress-6.7/class-wp-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-templates-registry.php index 8fd25e5439a11d..ec7635ead8f59b 100644 --- a/lib/compat/wordpress-6.7/class-wp-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-templates-registry.php @@ -100,7 +100,7 @@ public function register( $template_name, $args = array() ) { $template->status = 'publish'; $template->origin = 'plugin'; $template->is_custom = ! isset( $default_template_types[ $template_name ] ); - $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : ''; + $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : array(); } $this->registered_templates[ $template_name ] = $template; diff --git a/packages/e2e-tests/plugins/template-registration.php b/packages/e2e-tests/plugins/template-registration.php new file mode 100644 index 00000000000000..f37fbc65d04dca --- /dev/null +++ b/packages/e2e-tests/plugins/template-registration.php @@ -0,0 +1,72 @@ + 'Plugin Template', + 'description' => 'A template registered by a plugin.', + 'content' => '

This is a plugin-registered template.

', + 'post_types' => array( 'post' ), + ) + ); + add_action( + 'category_template_hierarchy', + function () { + return array( 'plugin-template' ); + } + ); + + // Custom template overridden by the theme. + wp_register_template( + 'gutenberg//custom-template', + array( + 'title' => 'Custom Template (overridden by the theme)', + 'description' => 'A custom template registered by a plugin and overridden by a theme.', + 'content' => '

This is a plugin-registered template and overridden by a theme.

', + 'post_types' => array( 'post' ), + ) + ); + + // Custom template used to test unregistration. + wp_register_template( + 'gutenberg//custom-unregistered-template', + array( + 'title' => 'Custom Unregistered Template', + 'description' => 'A custom template that is unregistered.', + 'content' => '

This is a plugin-registered template that is also unregistered.

', + ) + ); + wp_unregister_template( 'gutenberg//custom-unregistered-template' ); + + // Custom template used to test overriding default WP templates. + wp_register_template( + 'gutenberg//page', + array( + 'title' => 'Custom Page Template', + 'description' => 'A custom page template.', + 'content' => '

This is a plugin-registered page template.

', + ) + ); + + // Custom template used to test overriding default WP templates which can be created by the user. + wp_register_template( + 'gutenberg//author-admin', + array( + 'title' => 'Custom Author Template', + 'description' => 'A custom author template.', + 'content' => '

This is a plugin-registered author template.

', + ) + ); + } +); diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js new file mode 100644 index 00000000000000..58abcee7aad959 --- /dev/null +++ b/test/e2e/specs/site-editor/template-registration.spec.js @@ -0,0 +1,345 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.use( { + templateRegistrationUtils: async ( { editor, page }, use ) => { + await use( new TemplateRegistrationUtils( { editor, page } ) ); + }, +} ); + +test.describe( 'Template registration', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + await requestUtils.activatePlugin( + 'gutenberg-test-template-registration' + ); + } ); + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-template-registration' + ); + } ); + test.afterEach( async ( { requestUtils } ) => { + await requestUtils.deleteAllTemplates( 'wp_template' ); + } ); + + test( 'templates can be registered and edited', async ( { + admin, + editor, + page, + templateRegistrationUtils, + } ) => { + // Verify template is applied to the frontend. + await page.goto( '/?cat=1' ); + await expect( + page.getByText( 'This is a plugin-registered template.' ) + ).toBeVisible(); + + // Verify template is listed in the Site Editor. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + await expect( page.getByText( 'Plugin Template' ) ).toBeVisible(); + await expect( + page.getByText( 'A template registered by a plugin.' ) + ).toBeVisible(); + await expect( page.getByText( 'AuthorGutenberg' ) ).toBeVisible(); + + // Verify the template contents are rendered in the editor. + await page.getByText( 'Plugin Template' ).click(); + await expect( + editor.canvas.getByText( 'This is a plugin-registered template.' ) + ).toBeVisible(); + + // Verify edits persist in the frontend. + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'User-edited template' }, + } ); + await editor.saveSiteEditorEntities( { + isOnlyCurrentEntityDirty: true, + } ); + await page.goto( '/?cat=1' ); + await expect( page.getByText( 'User-edited template' ) ).toBeVisible(); + + // Verify template can be reset. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + const resetNotice = page + .getByLabel( 'Dismiss this notice' ) + .getByText( `"Plugin Template" reset.` ); + const savedButton = page.getByRole( 'button', { + name: 'Saved', + } ); + await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + const searchResults = page.getByLabel( 'Actions' ); + await searchResults.first().click(); + await page.getByRole( 'menuitem', { name: 'Reset' } ).click(); + await page.getByRole( 'button', { name: 'Reset' } ).click(); + + await expect( resetNotice ).toBeVisible(); + await expect( savedButton ).toBeVisible(); + await page.goto( '/?cat=1' ); + await expect( + page.getByText( 'Content edited template.' ) + ).toBeHidden(); + } ); + + test( 'registered templates are available in the Swap template screen', async ( { + admin, + editor, + page, + } ) => { + // Create a post. + await admin.visitAdminPage( '/post-new.php' ); + await page.getByLabel( 'Close', { exact: true } ).click(); + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'User-created post.' }, + } ); + + // Swap template. + await page.getByRole( 'button', { name: 'Post' } ).click(); + await page.getByLabel( 'Template options' ).click(); + await page.getByRole( 'menuitem', { name: 'Swap template' } ).click(); + await page.getByText( 'Plugin Template' ).click(); + + // Verify the template is applied. + const postId = await editor.publishPost(); + await page.goto( `?p=${ postId }` ); + await expect( + page.getByText( 'This is a plugin-registered template.' ) + ).toBeVisible(); + } ); + + test( 'themes can override registered templates', async ( { + admin, + editor, + page, + templateRegistrationUtils, + } ) => { + // Create a post. + await admin.visitAdminPage( '/post-new.php' ); + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'User-created post.' }, + } ); + + // Swap template. + await page.getByRole( 'button', { name: 'Post' } ).click(); + await page.getByLabel( 'Template options' ).click(); + await page.getByRole( 'menuitem', { name: 'Swap template' } ).click(); + await page.getByText( 'Custom', { exact: true } ).click(); + + // Verify the theme template is applied. + const postId = await editor.publishPost(); + await page.goto( `?p=${ postId }` ); + await expect( + page.getByText( 'Custom template for Posts' ) + ).toBeVisible(); + await expect( + page.getByText( + 'This is a plugin-registered template and overridden by a theme.' + ) + ).toBeHidden(); + + // Verify the plugin-registered template doesn't appear in the Site Editor. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + await templateRegistrationUtils.searchForTemplate( 'Custom Template' ); + await expect( + page.getByText( 'Custom Template (overridden by the theme)' ) + ).toBeHidden(); + } ); + + test( 'templates can be deleted if the registered plugin is deactivated', async ( { + admin, + editor, + page, + requestUtils, + templateRegistrationUtils, + } ) => { + // Make an edit to the template. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + await page.getByText( 'Plugin Template' ).click(); + await expect( + editor.canvas.getByText( 'This is a plugin-registered template.' ) + ).toBeVisible(); + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'User-customized template' }, + } ); + await editor.saveSiteEditorEntities( { + isOnlyCurrentEntityDirty: true, + } ); + + // Deactivate plugin. + await requestUtils.deactivatePlugin( + 'gutenberg-test-template-registration' + ); + + // Verify template can be deleted. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + const deletedNotice = page + .getByLabel( 'Dismiss this notice' ) + .getByText( `"Plugin Template" deleted.` ); + const savedButton = page.getByRole( 'button', { + name: 'Saved', + } ); + await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + const searchResults = page.getByLabel( 'Actions' ); + await searchResults.first().click(); + await page.getByRole( 'menuitem', { name: 'Delete' } ).click(); + await page.getByRole( 'button', { name: 'Delete' } ).click(); + + await expect( deletedNotice ).toBeVisible(); + await expect( savedButton ).toBeVisible(); + + // Expect template to no longer appear in the Site Editor. + await expect( page.getByLabel( 'Actions' ) ).toBeHidden(); + + // Reactivate plugin. + await requestUtils.activatePlugin( + 'gutenberg-test-template-registration' + ); + } ); + + test( 'registered templates can be unregistered', async ( { + admin, + page, + templateRegistrationUtils, + } ) => { + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + await templateRegistrationUtils.searchForTemplate( + 'Custom Unregistered Template' + ); + await expect( + page.getByText( 'Custom Unregistered Template' ) + ).toBeHidden(); + } ); + + test( 'WP default templates can be overridden by plugins', async ( { + page, + } ) => { + await page.goto( '?page_id=2' ); + await expect( + page.getByText( 'This is a plugin-registered page template.' ) + ).toBeVisible(); + } ); + + test( 'user-customized templates cannot be overridden by plugins', async ( { + admin, + editor, + page, + requestUtils, + templateRegistrationUtils, + } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-template-registration' + ); + + // Create an author template. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + await page.getByLabel( 'Add New Template' ).click(); + await page.getByRole( 'button', { name: 'Author Archives' } ).click(); + await page + .getByRole( 'button', { name: 'Author For a specific item' } ) + .click(); + await page.getByRole( 'option', { name: 'admin' } ).click(); + await expect( page.getByText( 'Choose a pattern' ) ).toBeVisible(); + await page.getByLabel( 'Close', { exact: true } ).click(); + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { content: 'Author template customized by the user.' }, + } ); + await editor.saveSiteEditorEntities( { + isOnlyCurrentEntityDirty: true, + } ); + + await requestUtils.activatePlugin( + 'gutenberg-test-template-registration' + ); + + // Verify the template edited by the user has priority over the one registered by the theme. + await page.goto( '?author=1' ); + await expect( + page.getByText( 'Author template customized by the user.' ) + ).toBeVisible(); + await expect( + page.getByText( 'This is a plugin-registered author template.' ) + ).toBeHidden(); + + // Verify the template registered by the plugin is not visible in the Site Editor either. + await admin.visitSiteEditor( { + postType: 'wp_template', + } ); + await templateRegistrationUtils.searchForTemplate( + 'Custom Author Template' + ); + await expect( page.getByText( 'Custom Author Template' ) ).toBeHidden(); + + // Reset the user-modified template. + const resetNotice = page + .getByLabel( 'Dismiss this notice' ) + .getByText( `"Author: Admin" reset.` ); + await page.getByPlaceholder( 'Search' ).fill( 'Author: admin' ); + await page.getByRole( 'link', { name: 'Author: Admin' } ).click(); + const actions = page.getByLabel( 'Actions' ); + await actions.first().click(); + await page.getByRole( 'menuitem', { name: 'Reset' } ).click(); + await page.getByRole( 'button', { name: 'Reset' } ).click(); + + await expect( resetNotice ).toBeVisible(); + + // Verify the template registered by the plugin is applied in the editor... + await expect( + editor.canvas.getByText( 'Author template customized by the user.' ) + ).toBeHidden(); + await expect( + editor.canvas.getByText( + 'This is a plugin-registered author template.' + ) + ).toBeVisible(); + + // ... and the frontend. + await page.goto( '?author=1' ); + await expect( + page.getByText( 'Author template customized by the user.' ) + ).toBeHidden(); + await expect( + page.getByText( 'This is a plugin-registered author template.' ) + ).toBeVisible(); + } ); +} ); + +class TemplateRegistrationUtils { + constructor( { page } ) { + this.page = page; + } + + async searchForTemplate( searchTerm ) { + const searchResults = this.page.getByLabel( 'Actions' ); + await expect + .poll( async () => await searchResults.count() ) + .toBeGreaterThan( 0 ); + const initialSearchResultsCount = await searchResults.count(); + await this.page.getByPlaceholder( 'Search' ).fill( searchTerm ); + await expect + .poll( async () => await searchResults.count() ) + .toBeLessThan( initialSearchResultsCount ); + } +} From de4ac59c803f54fbeff40a74a7790bd2d53b66ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Mon, 29 Jul 2024 15:52:12 +0200 Subject: [PATCH 36/49] Add e2e tests (II) --- .../e2e-tests/plugins/template-registration.php | 16 ++++++++-------- .../site-editor/template-registration.spec.js | 15 ++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/e2e-tests/plugins/template-registration.php b/packages/e2e-tests/plugins/template-registration.php index f37fbc65d04dca..e95e544399903a 100644 --- a/packages/e2e-tests/plugins/template-registration.php +++ b/packages/e2e-tests/plugins/template-registration.php @@ -40,21 +40,21 @@ function () { // Custom template used to test unregistration. wp_register_template( - 'gutenberg//custom-unregistered-template', + 'gutenberg//plugin-unregistered-template', array( - 'title' => 'Custom Unregistered Template', - 'description' => 'A custom template that is unregistered.', + 'title' => 'Plugin Unregistered Template', + 'description' => 'A plugin-registered template that is unregistered.', 'content' => '

This is a plugin-registered template that is also unregistered.

', ) ); - wp_unregister_template( 'gutenberg//custom-unregistered-template' ); + wp_unregister_template( 'gutenberg//plugin-unregistered-template' ); // Custom template used to test overriding default WP templates. wp_register_template( 'gutenberg//page', array( - 'title' => 'Custom Page Template', - 'description' => 'A custom page template.', + 'title' => 'Plugin Page Template', + 'description' => 'A plugin-registered page template.', 'content' => '

This is a plugin-registered page template.

', ) ); @@ -63,8 +63,8 @@ function () { wp_register_template( 'gutenberg//author-admin', array( - 'title' => 'Custom Author Template', - 'description' => 'A custom author template.', + 'title' => 'Plugin Author Template', + 'description' => 'A plugin-registered author template.', 'content' => '

This is a plugin-registered author template.

', ) ); diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js index 58abcee7aad959..37cc61db454b84 100644 --- a/test/e2e/specs/site-editor/template-registration.spec.js +++ b/test/e2e/specs/site-editor/template-registration.spec.js @@ -23,6 +23,7 @@ test.describe( 'Template registration', () => { } ); test.afterEach( async ( { requestUtils } ) => { await requestUtils.deleteAllTemplates( 'wp_template' ); + await requestUtils.deleteAllPosts(); } ); test( 'templates can be registered and edited', async ( { @@ -104,7 +105,7 @@ test.describe( 'Template registration', () => { // Swap template. await page.getByRole( 'button', { name: 'Post' } ).click(); - await page.getByLabel( 'Template options' ).click(); + await page.getByRole( 'button', { name: 'Template options' } ).click(); await page.getByRole( 'menuitem', { name: 'Swap template' } ).click(); await page.getByText( 'Plugin Template' ).click(); @@ -131,7 +132,7 @@ test.describe( 'Template registration', () => { // Swap template. await page.getByRole( 'button', { name: 'Post' } ).click(); - await page.getByLabel( 'Template options' ).click(); + await page.getByRole( 'button', { name: 'Template options' } ).click(); await page.getByRole( 'menuitem', { name: 'Swap template' } ).click(); await page.getByText( 'Custom', { exact: true } ).click(); @@ -223,10 +224,10 @@ test.describe( 'Template registration', () => { postType: 'wp_template', } ); await templateRegistrationUtils.searchForTemplate( - 'Custom Unregistered Template' + 'Plugin Unregistered Template' ); await expect( - page.getByText( 'Custom Unregistered Template' ) + page.getByText( 'Plugin Unregistered Template' ) ).toBeHidden(); } ); @@ -283,14 +284,14 @@ test.describe( 'Template registration', () => { page.getByText( 'This is a plugin-registered author template.' ) ).toBeHidden(); - // Verify the template registered by the plugin is not visible in the Site Editor either. + // Verify the template registered by the plugin is not visible in the Site Editor. await admin.visitSiteEditor( { postType: 'wp_template', } ); await templateRegistrationUtils.searchForTemplate( - 'Custom Author Template' + 'Plugin Author Template' ); - await expect( page.getByText( 'Custom Author Template' ) ).toBeHidden(); + await expect( page.getByText( 'Plugin Author Template' ) ).toBeHidden(); // Reset the user-modified template. const resetNotice = page From 00d9f40ee68e9d3174664e0fbb00d618d13cf905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Mon, 29 Jul 2024 15:55:22 +0200 Subject: [PATCH 37/49] Make sure correct template author is displayed --- lib/compat/wordpress-6.7/compat.php | 15 ++++++++++++--- .../site-editor/template-registration.spec.js | 4 +++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index 218f95b5df0568..717fcf583257aa 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -28,7 +28,10 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); if ( $registered_template ) { $query_result[ $key ]->plugin = $registered_template->plugin; - $query_result[ $key ]->origin = 'theme' !== $query_result[ $key ]->origin ? 'plugin' : $query_result[ $key ]->origin; + $query_result[ $key ]->origin = + 'theme' !== $query_result[ $key ]->origin && 'theme' !== $query_result[ $key ]->source ? + 'plugin' : + $query_result[ $key ]->origin; } } @@ -68,7 +71,10 @@ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; - $block_template->origin = 'theme' !== $block_template->origin ? 'plugin' : $block_template->origin; + $block_template->origin = + 'theme' !== $block_template->origin && 'theme' !== $block_template->source ? + 'plugin' : + $block_template->origin; } } @@ -88,7 +94,10 @@ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; - $block_template->origin = 'theme' !== $block_template->origin ? 'plugin' : $block_template->origin; + $block_template->origin = + 'theme' !== $block_template->origin && 'theme' !== $block_template->source ? + 'plugin' : + $block_template->origin; } return $block_template; } diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js index 37cc61db454b84..d76a92c7d4a559 100644 --- a/test/e2e/specs/site-editor/template-registration.spec.js +++ b/test/e2e/specs/site-editor/template-registration.spec.js @@ -152,10 +152,12 @@ test.describe( 'Template registration', () => { await admin.visitSiteEditor( { postType: 'wp_template', } ); - await templateRegistrationUtils.searchForTemplate( 'Custom Template' ); + await templateRegistrationUtils.searchForTemplate( 'Custom' ); await expect( page.getByText( 'Custom Template (overridden by the theme)' ) ).toBeHidden(); + // Verify the theme template shows the theme name as the author. + await expect( page.getByText( 'AuthorEmptytheme' ) ).toBeVisible(); } ); test( 'templates can be deleted if the registered plugin is deactivated', async ( { From 9232b1f1d43c99f4b6ce932f1d137c1b06fcd25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 30 Jul 2024 10:17:30 +0200 Subject: [PATCH 38/49] Update core-merge comments in class-gutenberg-rest-templates-controller-6-7.php --- .../class-gutenberg-rest-templates-controller-6-7.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index d75f37ae06ffdf..ac56df75b12168 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -42,6 +42,7 @@ public function get_item( $request ) { * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ + // @core-merge: Fix wrong author in plugin templates. public function prepare_item_for_response( $item, $request ) { $template = $item; @@ -50,7 +51,6 @@ public function prepare_item_for_response( $item, $request ) { if ( 'plugin' !== $item->origin ) { return parent::prepare_item_for_response( $item, $request ); } - // @core-merge: Fix wrong author in plugin templates. $cloned_item = clone $item; // Set the origin as theme when calling the previous `prepare_item_for_response()` to prevent warnings when generating the author text. $cloned_item->origin = 'theme'; @@ -99,7 +99,7 @@ public function prepare_item_for_response( $item, $request ) { * @param WP_Block_Template $template_object Template instance. * @return string Original source of the template one of theme, plugin, site, or user. */ - // @core-merge: Only the comments format (from inline to multi-line) has changed in this file. + // @core-merge: Changed the comments format (from inline to multi-line) in the entire function. private static function get_wp_templates_original_source_field( $template_object ) { if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { /* @@ -125,6 +125,7 @@ private static function get_wp_templates_original_source_field( $template_object } // Added by plugin. + // @core-merge: Removed `$template_object->has_theme_file` check from this if clause. if ( 'plugin' === $template_object->origin ) { return 'plugin'; } From 81f17c9f7347c2b36198aecbcdf4ad5b68bcd1bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Tue, 30 Jul 2024 13:29:58 +0200 Subject: [PATCH 39/49] Simplify logic to retrieve correct template when source is set to 'plugin' --- .../class-gutenberg-rest-templates-controller-6-7.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index ac56df75b12168..686171e4dd936c 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -17,13 +17,8 @@ class Gutenberg_REST_Templates_Controller_6_7 extends Gutenberg_REST_Templates_C * @return WP_REST_Response|WP_Error */ public function get_item( $request ) { - if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) { $template = get_block_file_template( $request['id'], $this->post_type ); - // @core-merge: Add a special case for plugin templates. - } elseif ( isset( $request['source'] ) && 'plugin' === $request['source'] ) { - list( , $slug ) = explode( '//', $request['id'] ); - $template = WP_Templates_Registry::get_instance()->get_by_slug( $slug ); - // @core-merge: End of changes to merge in core. } else { $template = get_block_template( $request['id'], $this->post_type ); } From ba73c17c148bfbe75d6f5a43f8036a4ea6585478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 31 Jul 2024 10:26:57 +0200 Subject: [PATCH 40/49] Use assertSame when possible and add error messages --- ...tenberg-rest-templates-controller-test.php | 24 ++++---- phpunit/class-wp-templates-registry-test.php | 58 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index af87b6f9c4e4a7..a32d94ca19a50f 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -37,27 +37,27 @@ public function test_get_item() { $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); - $this->assertNotWPError( $response ); + $this->assertNotWPError( $response, "Fetching a registered template shouldn't cause an error." ); $data = $response->get_data(); - $this->assertEquals( 'default//test-template', $data['id'] ); - $this->assertEquals( 'default', $data['theme'] ); - $this->assertEquals( 'Template content', $data['content']['raw'] ); - $this->assertEquals( 'test-template', $data['slug'] ); - $this->assertEquals( 'plugin', $data['source'] ); - $this->assertEquals( 'plugin', $data['origin'] ); - $this->assertEquals( 'Description of test template', $data['description'] ); - $this->assertEquals( 'Test Template', $data['title']['rendered'] ); - $this->assertEquals( 'test-plugin', $data['plugin'] ); + $this->assertSame( 'default//test-template', $data['id'], 'Template ID mismatch.' ); + $this->assertSame( 'default', $data['theme'], 'Template theme mismatch.' ); + $this->assertSame( 'Template content', $data['content']['raw'], 'Template content mismatch.' ); + $this->assertSame( 'test-template', $data['slug'], 'Template slug mismatch.' ); + $this->assertSame( 'plugin', $data['source'], "Template source should be 'plugin'." ); + $this->assertSame( 'plugin', $data['origin'], "Template origin should be 'plugin'." ); + $this->assertSame( 'Description of test template', $data['description'], 'Template description mismatch.' ); + $this->assertSame( 'Test Template', $data['title']['rendered'], 'Template title mismatch.' ); + $this->assertSame( 'test-plugin', $data['plugin'], 'Plugin name mismatch.' ); wp_unregister_template( $template_name ); $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); - $this->assertNotWPError( $response ); - $this->assertEquals( 404, $response->get_status() ); + $this->assertNotWPError( $response, "Fetching an unregistered template shouldn't cause an error." ); + $this->assertSame( 404, $response->get_status(), 'Fetching an unregistered template should return 404.' ); } /** diff --git a/phpunit/class-wp-templates-registry-test.php b/phpunit/class-wp-templates-registry-test.php index 5c0a7201164f62..b4fbb2224abf21 100644 --- a/phpunit/class-wp-templates-registry-test.php +++ b/phpunit/class-wp-templates-registry-test.php @@ -22,7 +22,7 @@ public function test_register_template() { $template_name = 'test-plugin//test-template'; $template = self::$registry->register( $template_name ); - $this->assertEquals( $template->slug, 'test-template' ); + $this->assertSame( $template->slug, 'test-template' ); self::$registry->unregister( $template_name ); } @@ -35,8 +35,8 @@ public function test_register_template_invalid_name() { $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); - $this->assertEquals( 'template_name_no_string', $result->get_error_code() ); - $this->assertEquals( 'Template names must be a string.', $result->get_error_message() ); + $this->assertSame( 'template_name_no_string', $result->get_error_code(), 'Error code mismatch.' ); + $this->assertSame( 'Template names must be a string.', $result->get_error_message(), 'Error message mismatch.' ); } public function test_register_template_invalid_name_uppercase() { @@ -47,8 +47,8 @@ public function test_register_template_invalid_name_uppercase() { $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); - $this->assertEquals( 'template_name_no_uppercase', $result->get_error_code() ); - $this->assertEquals( 'Template names must not contain uppercase characters.', $result->get_error_message() ); + $this->assertSame( 'template_name_no_uppercase', $result->get_error_code(), 'Error code mismatch.' ); + $this->assertSame( 'Template names must not contain uppercase characters.', $result->get_error_message(), 'Error message mismatch.' ); } public function test_register_template_no_prefix() { @@ -57,8 +57,8 @@ public function test_register_template_no_prefix() { $result = self::$registry->register( 'template-no-plugin', array() ); $this->assertWPError( $result ); - $this->assertEquals( 'template_no_prefix', $result->get_error_code() ); - $this->assertEquals( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', $result->get_error_message() ); + $this->assertSame( 'template_no_prefix', $result->get_error_code(), 'Error code mismatch.' ); + $this->assertSame( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', $result->get_error_message(), 'Error message mismatch.' ); } public function test_register_template_already_exists() { @@ -71,8 +71,8 @@ public function test_register_template_already_exists() { $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); - $this->assertEquals( 'template_already_registered', $result->get_error_code() ); - $this->assertStringContainsString( 'Template "test-plugin//duplicate-template" is already registered.', $result->get_error_message() ); + $this->assertSame( 'template_already_registered', $result->get_error_code(), 'Error code mismatch.' ); + $this->assertStringContainsString( 'Template "test-plugin//duplicate-template" is already registered.', $result->get_error_message(), 'Error message mismatch.' ); self::$registry->unregister( $template_name ); } @@ -85,10 +85,10 @@ public function test_get_all_registered() { $all_templates = self::$registry->get_all_registered(); - $this->assertIsArray( $all_templates ); - $this->assertCount( 2, $all_templates ); - $this->assertArrayHasKey( 'test-plugin//template-1', $all_templates ); - $this->assertArrayHasKey( 'test-plugin//template-2', $all_templates ); + $this->assertIsArray( $all_templates, 'Registered templates should be an array.' ); + $this->assertCount( 2, $all_templates, 'Registered templates should contain 2 items.' ); + $this->assertArrayHasKey( 'test-plugin//template-1', $all_templates, 'Registered templates should contain "test-plugin//template-1".' ); + $this->assertArrayHasKey( 'test-plugin//template-2', $all_templates, 'Registered templates should contain "test-plugin//template-2".' ); self::$registry->unregister( $template_name_1 ); self::$registry->unregister( $template_name_2 ); @@ -106,16 +106,16 @@ public function test_get_registered() { $registered_template = self::$registry->get_registered( $template_name ); - $this->assertEquals( 'default', $registered_template->theme ); - $this->assertEquals( 'registered-template', $registered_template->slug ); - $this->assertEquals( 'default//registered-template', $registered_template->id ); - $this->assertEquals( 'Registered Template', $registered_template->title ); - $this->assertEquals( 'Template content', $registered_template->content ); - $this->assertEquals( 'Description of registered template', $registered_template->description ); - $this->assertEquals( 'plugin', $registered_template->source ); - $this->assertEquals( 'plugin', $registered_template->origin ); - $this->assertEquals( array( 'post', 'page' ), $registered_template->post_types ); - $this->assertEquals( 'test-plugin', $registered_template->plugin ); + $this->assertSame( 'default', $registered_template->theme, 'Template theme mismatch.' ); + $this->assertSame( 'registered-template', $registered_template->slug, 'Template slug mismatch.' ); + $this->assertSame( 'default//registered-template', $registered_template->id, 'Template ID mismatch.' ); + $this->assertSame( 'Registered Template', $registered_template->title, 'Template title mismatch.' ); + $this->assertSame( 'Template content', $registered_template->content, 'Template content mismatch.' ); + $this->assertSame( 'Description of registered template', $registered_template->description, 'Template description mismatch.' ); + $this->assertSame( 'plugin', $registered_template->source, "Template source should be 'plugin'." ); + $this->assertSame( 'plugin', $registered_template->origin, "Template origin should be 'plugin'." ); + $this->assertEquals( array( 'post', 'page' ), $registered_template->post_types, 'Template post types mismatch.' ); + $this->assertSame( 'test-plugin', $registered_template->plugin, 'Plugin name mismatch.' ); self::$registry->unregister( $template_name ); } @@ -131,8 +131,8 @@ public function test_get_by_slug() { $registered_template = self::$registry->get_by_slug( $slug ); - $this->assertNotNull( $registered_template ); - $this->assertEquals( $slug, $registered_template->slug ); + $this->assertNotNull( $registered_template, 'Registered template should not be null.' ); + $this->assertSame( $slug, $registered_template->slug, 'Template slug mismatch.' ); self::$registry->unregister( $template_name ); } @@ -156,8 +156,8 @@ public function test_get_by_query() { ); $results = self::$registry->get_by_query( $query ); - $this->assertCount( 1, $results ); - $this->assertArrayHasKey( $template_name_1, $results ); + $this->assertCount( 1, $results, 'Query result should contain 1 item.' ); + $this->assertArrayHasKey( $template_name_1, $results, 'Query result should contain "test-plugin//query-template-1".' ); self::$registry->unregister( $template_name_1 ); self::$registry->unregister( $template_name_2 ); @@ -186,7 +186,7 @@ public function test_unregister() { $unregistered_template = self::$registry->unregister( $template_name ); - $this->assertEquals( $template, $unregistered_template ); - $this->assertFalse( self::$registry->is_registered( $template_name ) ); + $this->assertEquals( $template, $unregistered_template, 'Unregistered template should be the same as the registered one.' ); + $this->assertFalse( self::$registry->is_registered( $template_name ), 'Template should not be registered after unregistering.' ); } } From 3962e67f0529da6ffb4ac2a764addf195a66673f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 1 Aug 2024 12:56:37 +0200 Subject: [PATCH 41/49] Rename 'templates' to 'block templates' in several APIs --- lib/compat/wordpress-6.7/block-templates.php | 12 ++--- ...utenberg-rest-templates-controller-6-7.php | 2 +- ... => class-wp-block-templates-registry.php} | 8 +-- lib/compat/wordpress-6.7/compat.php | 10 ++-- lib/compat/wordpress-6.7/rest-api.php | 2 +- lib/load.php | 2 +- ...on.php => block-template-registration.php} | 16 +++--- phpunit/block-template-test.php | 8 +-- ...tenberg-rest-templates-controller-test.php | 4 +- ...lass-wp-block-templates-registry-test.php} | 18 +++---- .../site-editor/template-registration.spec.js | 52 +++++++++++-------- 11 files changed, 71 insertions(+), 63 deletions(-) rename lib/compat/wordpress-6.7/{class-wp-templates-registry.php => class-wp-block-templates-registry.php} (97%) rename packages/e2e-tests/plugins/{template-registration.php => block-template-registration.php} (89%) rename phpunit/{class-wp-templates-registry-test.php => class-wp-block-templates-registry-test.php} (93%) diff --git a/lib/compat/wordpress-6.7/block-templates.php b/lib/compat/wordpress-6.7/block-templates.php index b9518b681f75c4..e270ab226c1d9f 100644 --- a/lib/compat/wordpress-6.7/block-templates.php +++ b/lib/compat/wordpress-6.7/block-templates.php @@ -5,7 +5,7 @@ * @package gutenberg */ -if ( ! function_exists( 'wp_register_template' ) ) { +if ( ! function_exists( 'wp_register_block_template' ) ) { /** * Register a template. * @@ -23,19 +23,19 @@ * } * @return WP_Block_Template|WP_Error The registered template object on success, WP_Error object on failure. */ - function wp_register_template( $template_name, $args = array() ) { - return WP_Templates_Registry::get_instance()->register( $template_name, $args ); + function wp_register_block_template( $template_name, $args = array() ) { + return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args ); } } -if ( ! function_exists( 'wp_unregister_template' ) ) { +if ( ! function_exists( 'wp_unregister_block_template' ) ) { /** * Unregister a template. * * @param string $template_name Template name in the form of `plugin_uri//template_name`. * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist. */ - function wp_unregister_template( $template_name ) { - return WP_Templates_Registry::get_instance()->unregister( $template_name ); + function wp_unregister_block_template( $template_name ) { + return WP_Block_Templates_Registry::get_instance()->unregister( $template_name ); } } diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index 686171e4dd936c..d4c7796294b841 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -57,7 +57,7 @@ public function prepare_item_for_response( $item, $request ) { } if ( rest_is_field_included( 'plugin', $fields ) ) { - $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug ); + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug ); if ( $registered_template ) { $data['plugin'] = $registered_template->plugin; } diff --git a/lib/compat/wordpress-6.7/class-wp-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php similarity index 97% rename from lib/compat/wordpress-6.7/class-wp-templates-registry.php rename to lib/compat/wordpress-6.7/class-wp-block-templates-registry.php index ec7635ead8f59b..7bbc60f48c9801 100644 --- a/lib/compat/wordpress-6.7/class-wp-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -6,13 +6,13 @@ * @since 6.7.0 */ -if ( ! class_exists( 'WP_Templates_Registry' ) ) { +if ( ! class_exists( 'WP_Block_Templates_Registry' ) ) { /** * Core class used for interacting with templates. * * @since 6.7.0 */ - final class WP_Templates_Registry { + final class WP_Block_Templates_Registry { /** * Registered templates, as `$name => $instance` pairs. * @@ -25,7 +25,7 @@ final class WP_Templates_Registry { * Container for the main instance of the class. * * @since 6.7.0 - * @var WP_Templates_Registry|null + * @var WP_Block_Templates_Registry|null */ private static $instance = null; @@ -256,7 +256,7 @@ public function unregister( $template_name ) { * * @since 6.7.0 * - * @return WP_Templates_Registry The main instance. + * @return WP_Block_Templates_Registry The main instance. */ public static function get_instance() { if ( null === self::$instance ) { diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index 717fcf583257aa..7021cab2053eff 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -25,7 +25,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $template_type ) { // Add `plugin` property to templates registered by a plugin. foreach ( $query_result as $key => $value ) { - $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug ); if ( $registered_template ) { $query_result[ $key ]->plugin = $registered_template->plugin; $query_result[ $key ]->origin = @@ -41,7 +41,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t /* * Add templates registered in the template registry. Filtering out the ones which have a theme file. */ - $registered_templates = WP_Templates_Registry::get_instance()->get_by_query( $query ); + $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 ) { @@ -68,7 +68,7 @@ function ( $registered_template ) use ( $template_files ) { */ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { if ( $block_template ) { - $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; $block_template->origin = @@ -91,7 +91,7 @@ function _gutenberg_add_block_template_plugin_attribute( $block_template ) { */ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id ) { if ( $block_template ) { - $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug ); if ( $registered_template ) { $block_template->plugin = $registered_template->plugin; $block_template->origin = @@ -109,6 +109,6 @@ function _gutenberg_add_block_file_templates_from_registry( $block_template, $id } list( , $slug ) = $parts; - return WP_Templates_Registry::get_instance()->get_by_slug( $slug ); + return WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug ); } add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 2 ); diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php index c98f03c4257b17..fe2aac9c2580ae 100644 --- a/lib/compat/wordpress-6.7/rest-api.php +++ b/lib/compat/wordpress-6.7/rest-api.php @@ -61,7 +61,7 @@ function gutenberg_register_wp_rest_templates_controller_plugin_field() { array( 'get_callback' => function ( $template_object ) { if ( $template_object ) { - $registered_template = WP_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] ); + $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] ); if ( $registered_template ) { return $registered_template->plugin; } diff --git a/lib/load.php b/lib/load.php index 16c81d272661df..b501f0abd1c978 100644 --- a/lib/load.php +++ b/lib/load.php @@ -106,7 +106,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.7/blocks.php'; require __DIR__ . '/compat/wordpress-6.7/block-bindings.php'; require __DIR__ . '/compat/wordpress-6.7/script-modules.php'; -require __DIR__ . '/compat/wordpress-6.7/class-wp-templates-registry.php'; +require __DIR__ . '/compat/wordpress-6.7/class-wp-block-templates-registry.php'; require __DIR__ . '/compat/wordpress-6.7/compat.php'; // Experimental features. diff --git a/packages/e2e-tests/plugins/template-registration.php b/packages/e2e-tests/plugins/block-template-registration.php similarity index 89% rename from packages/e2e-tests/plugins/template-registration.php rename to packages/e2e-tests/plugins/block-template-registration.php index e95e544399903a..a7c75552849658 100644 --- a/packages/e2e-tests/plugins/template-registration.php +++ b/packages/e2e-tests/plugins/block-template-registration.php @@ -1,17 +1,17 @@ 'Plugin Template', @@ -28,7 +28,7 @@ function () { ); // Custom template overridden by the theme. - wp_register_template( + wp_register_block_template( 'gutenberg//custom-template', array( 'title' => 'Custom Template (overridden by the theme)', @@ -39,7 +39,7 @@ function () { ); // Custom template used to test unregistration. - wp_register_template( + wp_register_block_template( 'gutenberg//plugin-unregistered-template', array( 'title' => 'Plugin Unregistered Template', @@ -47,10 +47,10 @@ function () { 'content' => '

This is a plugin-registered template that is also unregistered.

', ) ); - wp_unregister_template( 'gutenberg//plugin-unregistered-template' ); + wp_unregister_block_template( 'gutenberg//plugin-unregistered-template' ); // Custom template used to test overriding default WP templates. - wp_register_template( + wp_register_block_template( 'gutenberg//page', array( 'title' => 'Plugin Page Template', @@ -60,7 +60,7 @@ function () { ); // Custom template used to test overriding default WP templates which can be created by the user. - wp_register_template( + wp_register_block_template( 'gutenberg//author-admin', array( 'title' => 'Plugin Author Template', diff --git a/phpunit/block-template-test.php b/phpunit/block-template-test.php index c1a4ed78188491..6589aad90b8053 100644 --- a/phpunit/block-template-test.php +++ b/phpunit/block-template-test.php @@ -11,13 +11,13 @@ public function set_up() { public function test_get_block_templates_from_registry() { $template_name = 'test-plugin//test-template'; - wp_register_template( $template_name ); + wp_register_block_template( $template_name ); $templates = get_block_templates(); $this->assertArrayHasKey( $template_name, $templates ); - wp_unregister_template( $template_name ); + wp_unregister_block_template( $template_name ); } public function test_get_block_template_from_registry() { @@ -26,12 +26,12 @@ public function test_get_block_template_from_registry() { 'title' => 'Test Template', ); - wp_register_template( $template_name, $args ); + wp_register_block_template( $template_name, $args ); $template = get_block_template( 'block-theme//test-template' ); $this->assertEquals( 'Test Template', $template->title ); - wp_unregister_template( $template_name ); + wp_unregister_block_template( $template_name ); } } diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index a32d94ca19a50f..05107f28e07cf7 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -32,7 +32,7 @@ public function test_get_item() { 'post_types' => array( 'post', 'page' ), ); - wp_register_template( $template_name, $args ); + wp_register_block_template( $template_name, $args ); $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); @@ -51,7 +51,7 @@ public function test_get_item() { $this->assertSame( 'Test Template', $data['title']['rendered'], 'Template title mismatch.' ); $this->assertSame( 'test-plugin', $data['plugin'], 'Plugin name mismatch.' ); - wp_unregister_template( $template_name ); + wp_unregister_block_template( $template_name ); $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' ); $response = rest_get_server()->dispatch( $request ); diff --git a/phpunit/class-wp-templates-registry-test.php b/phpunit/class-wp-block-templates-registry-test.php similarity index 93% rename from phpunit/class-wp-templates-registry-test.php rename to phpunit/class-wp-block-templates-registry-test.php index b4fbb2224abf21..56aea0a591ac92 100644 --- a/phpunit/class-wp-templates-registry-test.php +++ b/phpunit/class-wp-block-templates-registry-test.php @@ -1,20 +1,20 @@ setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' ); $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); @@ -43,7 +43,7 @@ public function test_register_template_invalid_name_uppercase() { // Try to register a template with uppercase characters in the name. $template_name = 'test-plugin//Invalid-Template-Name'; - $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' ); $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); @@ -53,7 +53,7 @@ public function test_register_template_invalid_name_uppercase() { public function test_register_template_no_prefix() { // Try to register a template without a namespace. - $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' ); $result = self::$registry->register( 'template-no-plugin', array() ); $this->assertWPError( $result ); @@ -67,7 +67,7 @@ public function test_register_template_already_exists() { self::$registry->register( $template_name ); // Try to register the same template again. - $this->setExpectedIncorrectUsage( 'WP_Templates_Registry::register' ); + $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' ); $result = self::$registry->register( $template_name ); $this->assertWPError( $result ); diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js index d76a92c7d4a559..132e3a8c49a902 100644 --- a/test/e2e/specs/site-editor/template-registration.spec.js +++ b/test/e2e/specs/site-editor/template-registration.spec.js @@ -4,21 +4,21 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); test.use( { - templateRegistrationUtils: async ( { editor, page }, use ) => { - await use( new TemplateRegistrationUtils( { editor, page } ) ); + blockTemplateRegistrationUtils: async ( { editor, page }, use ) => { + await use( new BlockTemplateRegistrationUtils( { editor, page } ) ); }, } ); -test.describe( 'Template registration', () => { +test.describe( 'Block template registration', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); await requestUtils.activatePlugin( - 'gutenberg-test-template-registration' + 'gutenberg-test-block-template-registration' ); } ); test.afterAll( async ( { requestUtils } ) => { await requestUtils.deactivatePlugin( - 'gutenberg-test-template-registration' + 'gutenberg-test-block-template-registration' ); } ); test.afterEach( async ( { requestUtils } ) => { @@ -30,7 +30,7 @@ test.describe( 'Template registration', () => { admin, editor, page, - templateRegistrationUtils, + blockTemplateRegistrationUtils, } ) => { // Verify template is applied to the frontend. await page.goto( '/?cat=1' ); @@ -42,7 +42,9 @@ test.describe( 'Template registration', () => { await admin.visitSiteEditor( { postType: 'wp_template', } ); - await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + await blockTemplateRegistrationUtils.searchForTemplate( + 'Plugin Template' + ); await expect( page.getByText( 'Plugin Template' ) ).toBeVisible(); await expect( page.getByText( 'A template registered by a plugin.' ) @@ -76,7 +78,9 @@ test.describe( 'Template registration', () => { const savedButton = page.getByRole( 'button', { name: 'Saved', } ); - await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + await blockTemplateRegistrationUtils.searchForTemplate( + 'Plugin Template' + ); const searchResults = page.getByLabel( 'Actions' ); await searchResults.first().click(); await page.getByRole( 'menuitem', { name: 'Reset' } ).click(); @@ -121,7 +125,7 @@ test.describe( 'Template registration', () => { admin, editor, page, - templateRegistrationUtils, + blockTemplateRegistrationUtils, } ) => { // Create a post. await admin.visitAdminPage( '/post-new.php' ); @@ -152,7 +156,7 @@ test.describe( 'Template registration', () => { await admin.visitSiteEditor( { postType: 'wp_template', } ); - await templateRegistrationUtils.searchForTemplate( 'Custom' ); + await blockTemplateRegistrationUtils.searchForTemplate( 'Custom' ); await expect( page.getByText( 'Custom Template (overridden by the theme)' ) ).toBeHidden(); @@ -165,13 +169,15 @@ test.describe( 'Template registration', () => { editor, page, requestUtils, - templateRegistrationUtils, + blockTemplateRegistrationUtils, } ) => { // Make an edit to the template. await admin.visitSiteEditor( { postType: 'wp_template', } ); - await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + await blockTemplateRegistrationUtils.searchForTemplate( + 'Plugin Template' + ); await page.getByText( 'Plugin Template' ).click(); await expect( editor.canvas.getByText( 'This is a plugin-registered template.' ) @@ -186,7 +192,7 @@ test.describe( 'Template registration', () => { // Deactivate plugin. await requestUtils.deactivatePlugin( - 'gutenberg-test-template-registration' + 'gutenberg-test-block-template-registration' ); // Verify template can be deleted. @@ -199,7 +205,9 @@ test.describe( 'Template registration', () => { const savedButton = page.getByRole( 'button', { name: 'Saved', } ); - await templateRegistrationUtils.searchForTemplate( 'Plugin Template' ); + await blockTemplateRegistrationUtils.searchForTemplate( + 'Plugin Template' + ); const searchResults = page.getByLabel( 'Actions' ); await searchResults.first().click(); await page.getByRole( 'menuitem', { name: 'Delete' } ).click(); @@ -213,19 +221,19 @@ test.describe( 'Template registration', () => { // Reactivate plugin. await requestUtils.activatePlugin( - 'gutenberg-test-template-registration' + 'gutenberg-test-block-template-registration' ); } ); test( 'registered templates can be unregistered', async ( { admin, page, - templateRegistrationUtils, + blockTemplateRegistrationUtils, } ) => { await admin.visitSiteEditor( { postType: 'wp_template', } ); - await templateRegistrationUtils.searchForTemplate( + await blockTemplateRegistrationUtils.searchForTemplate( 'Plugin Unregistered Template' ); await expect( @@ -247,10 +255,10 @@ test.describe( 'Template registration', () => { editor, page, requestUtils, - templateRegistrationUtils, + blockTemplateRegistrationUtils, } ) => { await requestUtils.deactivatePlugin( - 'gutenberg-test-template-registration' + 'gutenberg-test-block-template-registration' ); // Create an author template. @@ -274,7 +282,7 @@ test.describe( 'Template registration', () => { } ); await requestUtils.activatePlugin( - 'gutenberg-test-template-registration' + 'gutenberg-test-block-template-registration' ); // Verify the template edited by the user has priority over the one registered by the theme. @@ -290,7 +298,7 @@ test.describe( 'Template registration', () => { await admin.visitSiteEditor( { postType: 'wp_template', } ); - await templateRegistrationUtils.searchForTemplate( + await blockTemplateRegistrationUtils.searchForTemplate( 'Plugin Author Template' ); await expect( page.getByText( 'Plugin Author Template' ) ).toBeHidden(); @@ -329,7 +337,7 @@ test.describe( 'Template registration', () => { } ); } ); -class TemplateRegistrationUtils { +class BlockTemplateRegistrationUtils { constructor( { page } ) { this.page = page; } From efc68478b3726599d1fd8bb7a056dfef9504606b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 1 Aug 2024 16:27:35 +0200 Subject: [PATCH 42/49] Add backport changelog file --- backport-changelog/6.7/7125.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/6.7/7125.md diff --git a/backport-changelog/6.7/7125.md b/backport-changelog/6.7/7125.md new file mode 100644 index 00000000000000..ce208decd2d145 --- /dev/null +++ b/backport-changelog/6.7/7125.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7125 + +* https://github.com/WordPress/gutenberg/pull/61577 From d483308c27dd68551b1b0f9996c027255228a4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Wed, 7 Aug 2024 16:17:45 +0200 Subject: [PATCH 43/49] Change default author text from theme slug to the plugin slug --- .../class-gutenberg-rest-templates-controller-6-7.php | 4 +++- phpunit/class-gutenberg-rest-templates-controller-test.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index d4c7796294b841..ed67dded75ecb1 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -186,7 +186,9 @@ private static function get_wp_templates_author_text_field( $template_object ) { if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { return $plugins[ $plugin_basename ]['Name']; } - return $template_object->theme; + return isset( $template_object->plugin ) ? + $template_object->plugin : + $template_object->theme; // @core-merge: End of changes to merge in core. case 'site': return get_bloginfo( 'name' ); diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index 05107f28e07cf7..14735246c6fb20 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -47,6 +47,7 @@ public function test_get_item() { $this->assertSame( 'test-template', $data['slug'], 'Template slug mismatch.' ); $this->assertSame( 'plugin', $data['source'], "Template source should be 'plugin'." ); $this->assertSame( 'plugin', $data['origin'], "Template origin should be 'plugin'." ); + $this->assertSame( 'test-plugin', $data['author_text'], 'Template author text mismatch.' ); $this->assertSame( 'Description of test template', $data['description'], 'Template description mismatch.' ); $this->assertSame( 'Test Template', $data['title']['rendered'], 'Template title mismatch.' ); $this->assertSame( 'test-plugin', $data['plugin'], 'Plugin name mismatch.' ); From b76c6769572653cc8b6148364cfdd5bef85dbf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 8 Aug 2024 10:01:05 +0200 Subject: [PATCH 44/49] Remove unnecessary usage of empty Co-authored-by: Timothy Jacobs --- .../wordpress-6.7/class-wp-block-templates-registry.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php index 7bbc60f48c9801..44e8367e326a75 100644 --- a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -193,15 +193,15 @@ public function get_by_query( $query = array() ) { $matching_templates = array(); foreach ( $all_templates as $template_name => $template ) { - if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include, true ) ) { + if ( $slugs_to_include && ! in_array( $template->slug, $slugs_to_include, true ) ) { continue; } - if ( ! empty( $slugs_to_skip ) && in_array( $template->slug, $slugs_to_skip, true ) ) { + if ( $slugs_to_skip && in_array( $template->slug, $slugs_to_skip, true ) ) { continue; } - if ( ! empty( $post_types ) && ! in_array( $post_type, $template->post_types, true ) ) { + if ( $post_types && ! in_array( $post_type, $template->post_types, true ) ) { continue; } From 0ec44e16821f03777ccbd76e2f5af8dc0f90db7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 8 Aug 2024 10:03:28 +0200 Subject: [PATCH 45/49] DRY when validating registry input --- .../class-wp-block-templates-registry.php | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php index 44e8367e326a75..60571ca89e4f9c 100644 --- a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -42,43 +42,30 @@ public function register( $template_name, $args = array() ) { $template = null; + $error_message = ''; + $error_code = ''; if ( ! is_string( $template_name ) ) { - _doing_it_wrong( - __METHOD__, - __( 'Template names must be a string.', 'gutenberg' ), - '6.7.0' - ); - return new WP_Error( 'template_name_no_string', __( 'Template names must be a string.', 'gutenberg' ) ); - } - - if ( preg_match( '/[A-Z]+/', $template_name ) ) { - _doing_it_wrong( - __METHOD__, - __( 'Template names must not contain uppercase characters.', 'gutenberg' ), - '6.7.0' - ); - return new WP_Error( 'template_name_no_uppercase', __( 'Template names must not contain uppercase characters.', 'gutenberg' ) ); - } - - $name_matcher = '/^[a-z0-9-]+\/\/[a-z0-9-]+$/'; - if ( ! preg_match( $name_matcher, $template_name ) ) { - _doing_it_wrong( - __METHOD__, - __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ), - '6.7.0' - ); - return new WP_Error( 'template_no_prefix', __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ) ); + $error_message = __( 'Template names must be a string.', 'gutenberg' ); + $error_code = 'template_name_no_string'; + } elseif ( preg_match( '/[A-Z]+/', $template_name ) ) { + $error_message = __( 'Template names must not contain uppercase characters.', 'gutenberg' ); + $error_code = 'template_name_no_uppercase'; + } elseif ( ! preg_match( '/^[a-z0-9-]+\/\/[a-z0-9-]+$/', $template_name ) ) { + $error_message = __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' ); + $error_code = 'template_no_prefix'; + } elseif ( $this->is_registered( $template_name ) ) { + /* translators: %s: Template name. */ + $error_message = sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ); + $error_code = 'template_already_registered'; } - if ( $this->is_registered( $template_name ) ) { + if ( $error_message ) { _doing_it_wrong( __METHOD__, - /* translators: %s: Template name. */ - sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ), + $error_message, '6.7.0' ); - /* translators: %s: Template name. */ - return new WP_Error( 'template_already_registered', sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name ) ); + return new WP_Error( $error_code, $error_message ); } if ( ! $template ) { From 2c5d734650f4b91e1d7b766439dbc45f1f1dba6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 8 Aug 2024 10:08:35 +0200 Subject: [PATCH 46/49] Fix typo in error message --- lib/compat/wordpress-6.7/class-wp-block-templates-registry.php | 2 +- phpunit/class-wp-block-templates-registry-test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php index 60571ca89e4f9c..ded5f0237ee839 100644 --- a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -45,7 +45,7 @@ public function register( $template_name, $args = array() ) { $error_message = ''; $error_code = ''; if ( ! is_string( $template_name ) ) { - $error_message = __( 'Template names must be a string.', 'gutenberg' ); + $error_message = __( 'Template names must be strings.', 'gutenberg' ); $error_code = 'template_name_no_string'; } elseif ( preg_match( '/[A-Z]+/', $template_name ) ) { $error_message = __( 'Template names must not contain uppercase characters.', 'gutenberg' ); diff --git a/phpunit/class-wp-block-templates-registry-test.php b/phpunit/class-wp-block-templates-registry-test.php index 56aea0a591ac92..fb8436eb6153d4 100644 --- a/phpunit/class-wp-block-templates-registry-test.php +++ b/phpunit/class-wp-block-templates-registry-test.php @@ -36,7 +36,7 @@ public function test_register_template_invalid_name() { $this->assertWPError( $result ); $this->assertSame( 'template_name_no_string', $result->get_error_code(), 'Error code mismatch.' ); - $this->assertSame( 'Template names must be a string.', $result->get_error_message(), 'Error message mismatch.' ); + $this->assertSame( 'Template names must be strings.', $result->get_error_message(), 'Error message mismatch.' ); } public function test_register_template_invalid_name_uppercase() { From b5a3de4a1fd1bdcba8b4f249782dd417e0c3c397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 8 Aug 2024 10:08:43 +0200 Subject: [PATCH 47/49] Fix typo in variable name --- lib/compat/wordpress-6.7/class-wp-block-templates-registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php index ded5f0237ee839..178777a2ff1b0e 100644 --- a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -188,7 +188,7 @@ public function get_by_query( $query = array() ) { continue; } - if ( $post_types && ! in_array( $post_type, $template->post_types, true ) ) { + if ( $post_type && ! in_array( $post_type, $template->post_types, true ) ) { continue; } From 336c0a3b08b6e2ed3d413693326325d10349d91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 8 Aug 2024 10:10:55 +0200 Subject: [PATCH 48/49] Fix wrong register method signature Co-authored-by: Timothy Jacobs --- lib/compat/wordpress-6.7/class-wp-block-templates-registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php index 178777a2ff1b0e..db53f735e13b3d 100644 --- a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php +++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php @@ -36,7 +36,7 @@ final class WP_Block_Templates_Registry { * * @param string $template_name Template name including namespace. * @param array $args Optional. Array of template arguments. - * @return WP_Block_Template|false The registered template on success, or false on failure. + * @return WP_Block_Template|WP_Error The registered template on success, or false on failure. */ public function register( $template_name, $args = array() ) { From 7eb235c388d9c56614c2b5654f00568ae0f2e089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Thu, 8 Aug 2024 16:41:33 +0200 Subject: [PATCH 49/49] Fix 'plugin' property added to TemplatePart type instead of Template after a wrong rebase --- packages/editor/src/dataviews/actions/reset-post.tsx | 3 ++- packages/editor/src/dataviews/actions/utils.ts | 2 +- packages/editor/src/dataviews/types.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/dataviews/actions/reset-post.tsx b/packages/editor/src/dataviews/actions/reset-post.tsx index 90c981f3bfdcca..cc4cea8f5c82c0 100644 --- a/packages/editor/src/dataviews/actions/reset-post.tsx +++ b/packages/editor/src/dataviews/actions/reset-post.tsx @@ -32,7 +32,8 @@ const resetPost: Action< Post > = { return ( isTemplateOrTemplatePart( item ) && item?.source === TEMPLATE_ORIGINS.custom && - ( Boolean( item?.plugin ) || item?.has_theme_file ) + ( Boolean( item.type === 'wp_template' && item?.plugin ) || + item?.has_theme_file ) ); }, icon: backup, diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts index c9d363d4551154..33a2be16397f3f 100644 --- a/packages/editor/src/dataviews/actions/utils.ts +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -58,7 +58,7 @@ export function isTemplateRemovable( template: Template | TemplatePart ) { [ template.source, template.source ].includes( TEMPLATE_ORIGINS.custom ) && - ! Boolean( template?.plugin ) && + ! Boolean( template.type === 'wp_template' && template?.plugin ) && ! template.has_theme_file ); } diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index af640649a31de9..d207410ca2b6a5 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -34,6 +34,8 @@ export interface Template extends CommonPost { type: 'wp_template'; is_custom: boolean; source: string; + origin: string; + plugin?: string; has_theme_file: boolean; id: string; } @@ -42,7 +44,6 @@ export interface TemplatePart extends CommonPost { type: 'wp_template_part'; source: string; origin: string; - plugin?: string; has_theme_file: boolean; id: string; area: string;