Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plugin template registration API #61577

Merged
merged 49 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a4f1949
Add plugin template registration API
Aljullu Jun 3, 2024
b9d439b
Remove support for template parts in the registry
Aljullu Jun 26, 2024
90669a8
Rename API from gutenberg_register_block_template() to gutenberg_regi…
Aljullu Jun 26, 2024
e1612d6
Add @core-merge comment specifying a function hasn't been modified
Aljullu Jun 26, 2024
8d56d84
Update WP_Block_Templates_Registry so it returns an error when using …
Aljullu Jul 4, 2024
1b7292b
Use wp_parse_args() instead of custom checks
Aljullu Jul 4, 2024
474cd8a
Add back code removed by mistake in a previous commit
Aljullu Jul 4, 2024
9c1016c
Add @core-merge comment specifying a function hasn't been modified
Aljullu Jul 4, 2024
d31655f
Use wp_get_active_and_valid_plugins() instead of get_plugins() to ret…
Aljullu Jul 4, 2024
d9d7bf5
Refactor how we get the plugin name so it works with plugins that fol…
Aljullu Jul 4, 2024
69e340c
Fix registered templates beind 'removable' instead of 'revertable'
Aljullu Jul 4, 2024
736b0a8
PHP linting
Aljullu Jul 4, 2024
5e6719f
Rely on template.plugin instead of template.origin to identify plugin…
Aljullu Jul 8, 2024
83430f5
Replace more instances of 'block templates' with simply 'templates'
Aljullu Jul 16, 2024
934d358
Avoid usage of unset()
Aljullu Jul 16, 2024
e2006a3
Fix warning when trying to retrieve the wrong plugin
Aljullu Jul 16, 2024
93d1698
Update comments format
Aljullu Jul 16, 2024
3f16162
Refactor how we add the 'plugin' property
Aljullu Jul 16, 2024
d8b545f
Rename WP_Templates_Registry
Aljullu Jul 16, 2024
8de5b47
Fix alignment
Aljullu Jul 16, 2024
64aad98
Remove has_theme_file=true property from plugin-registered templates
Aljullu Jul 16, 2024
e13c5a6
Inline get_callback function
Aljullu Jul 17, 2024
6e11c6b
Remove support for registering templates with an object
Aljullu Jul 17, 2024
0a3e58f
Refactor API so no prefix is needed and the slug prop is also unneces…
Aljullu Jul 17, 2024
94f1f2d
Correctly set is_custom prop
Aljullu Jul 17, 2024
4972b25
Make sure plugin.php has been intitialized when trying to access plugins
Aljullu Jul 17, 2024
231a82b
Add docblock to gutenberg_register_template() and create gutenberg_un…
Aljullu Jul 17, 2024
1082b14
Linting
Aljullu Jul 17, 2024
3c4d9ba
Fix typo
Aljullu Jul 17, 2024
c021b3f
Make sure plugin.php has been intitialized when trying to access plug…
Aljullu Jul 17, 2024
1f8274b
Fix wrong 'origin' set in plugin templates that were customized by th…
Aljullu Jul 18, 2024
469e680
Add PHP unit tests
Aljullu Jul 24, 2024
44ce898
Move files under WP 6.7 folder and rename API functions from gutenber…
Aljullu Jul 26, 2024
ae53a3a
Add back template namespace and remove plugin property
Aljullu Jul 26, 2024
e8fb1ea
Add e2e tests
Aljullu Jul 29, 2024
de4ac59
Add e2e tests (II)
Aljullu Jul 29, 2024
00d9f40
Make sure correct template author is displayed
Aljullu Jul 29, 2024
9232b1f
Update core-merge comments in class-gutenberg-rest-templates-controll…
Aljullu Jul 30, 2024
81f17c9
Simplify logic to retrieve correct template when source is set to 'pl…
Aljullu Jul 30, 2024
ba73c17
Use assertSame when possible and add error messages
Aljullu Jul 31, 2024
3962e67
Rename 'templates' to 'block templates' in several APIs
Aljullu Aug 1, 2024
efc6847
Add backport changelog file
Aljullu Aug 1, 2024
d483308
Change default author text from theme slug to the plugin slug
Aljullu Aug 7, 2024
b76c676
Remove unnecessary usage of empty
Aljullu Aug 8, 2024
0ec44e1
DRY when validating registry input
Aljullu Aug 8, 2024
2c5d734
Fix typo in error message
Aljullu Aug 8, 2024
b5a3de4
Fix typo in variable name
Aljullu Aug 8, 2024
336c0a3
Fix wrong register method signature
Aljullu Aug 8, 2024
7eb235c
Fix 'plugin' property added to TemplatePart type instead of Template …
Aljullu Aug 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backport-changelog/6.7/7125.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/7125

* https://github.com/WordPress/gutenberg/pull/61577
41 changes: 41 additions & 0 deletions lib/compat/wordpress-6.7/block-templates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* Block template functions.
*
* @package gutenberg
*/

if ( ! function_exists( 'wp_register_block_template' ) ) {
/**
* Register a template.
*
* @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
* 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 wp_register_block_template( $template_name, $args = array() ) {
return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args );
}
}

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_block_template( $template_name ) {
return WP_Block_Templates_Registry::get_instance()->unregister( $template_name );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clarify to me what's different in the endpoint compare trunk? It's not really clear because of how the code is extended.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are some @core-merge comments in this file (I updated them in the last commit). Every function should have a comment either at the top of the function or inline detailing the changes. Is that enough, or is there a better way to document this?

/**
* REST API: Gutenberg_REST_Templates_Controller_6_7 class
*
* @package gutenberg
*/

/**
* Gutenberg_REST_Templates_Controller_6_7 class
*
*/
class Gutenberg_REST_Templates_Controller_6_7 extends Gutenberg_REST_Templates_Controller_6_6 {
/**
* Returns the given template
*
* @param WP_REST_Request $request The request instance.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) {
$template = get_block_file_template( $request['id'], $this->post_type );
} 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.
*/
// @core-merge: Fix wrong author in plugin templates.
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 );
}
$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( '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 );
}

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.
*/
// @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 ) {
Aljullu marked this conversation as resolved.
Show resolved Hide resolved
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.
// @core-merge: Removed `$template_object->has_theme_file` check from this if clause.
if ( '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':
// @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 ) ) {
Aljullu marked this conversation as resolved.
Show resolved Hide resolved
$plugins = wp_get_active_and_valid_plugins();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
$plugins = wp_get_active_and_valid_plugins();
$plugins = get_plugins();

No need to have the active and valid check. We just need to find the plugin data.

Copy link
Contributor

Choose a reason for hiding this comment

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

@TimothyBJacobs This was an earlier suggestion of mine. get_plugins() scans all the files in the plugin directory so is quite expensive. wp_get_active_and_valid_plugins() uses data from the options table so is significantly faster.

As the registered templates will be from active plugins I think it's fine to use the faster function but there's a chance I am missing something. Am I?

Copy link
Member

Choose a reason for hiding this comment

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

My thinking is get_plugins caches after the first time through, and will also pull the plugin data for us which we are going to need. It also looks like there already is a call to get_plugins? I'm not entirely sure what code path that's for.

I could definitely be wrong there, though. It's probably worth pulling into a method and benching it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It also looks like there already is a call to get_plugins? I'm not entirely sure what code path that's for.

That's basically to "fall back" to the current behavior in trunk:

https://github.com/WordPress/wordpress-develop/blob/9f09c574c0ac64041c81760f10646994d21ce49d/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php#L868

I could refactor it to use wp_get_active_and_valid_plugins() if needed. I don't have a strong opinion between get_plugins() and wp_get_active_and_valid_plugins(), though.

Copy link
Contributor

Choose a reason for hiding this comment

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

@TimothyBJacobs is this a blocker for this PR to land? It doesn't seem a big change to commit your suggestion and in general I think we would be usually at the active plugin use case, so maybe wp_get_active_and_valid_plugins is a bit lighter?


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;
}
}
}

/*
* 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_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 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' );
case 'user':
$author = get_user_by( 'id', $template_object->author );
if ( ! $author ) {
return __( 'Unknown author' );
}
return $author->get( 'display_name' );
peterwilsoncc marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading
Loading