Skip to content

Commit

Permalink
Add plugin template registration API
Browse files Browse the repository at this point in the history
  • Loading branch information
Aljullu committed May 10, 2024
1 parent d489fb3 commit 748e203
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 6 deletions.
14 changes: 14 additions & 0 deletions lib/block-templates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/**
* Block template functions.
*
* @package gutenberg
*/

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 );
}
246 changes: 246 additions & 0 deletions lib/class-wp-block-templates-registry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
<?php
/**
* Block template functions.
*
* @package gutenberg
*/

final class WP_Block_Templates_Registry {

Check failure on line 8 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

The "WP_Block_Templates_Registry" class should be guarded against redeclaration.
private $registered_block_templates = array( 'wp_template' => array(), 'wp_template_part' => array() );

Check failure on line 9 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

When a multi-item array uses associative keys, each value should start on a new line.
private static $instance = null;

Check warning on line 10 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

public function register( $template_name, $template_type, $args = array() ) {

$template = null;
if ( $template_name instanceof WP_Block_Template ) {
$template = $template_name;

Check warning on line 16 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space
$template_name = $template->name;

Check warning on line 17 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 1 space but found 7 spaces
}

if ( ! is_string( $template_name ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Block template names must be strings.' ),

Check warning on line 23 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Missing $domain parameter in function call to __(). If this text string is supposed to use a WP Core translation, use the "default" text domain.
'6.6.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.' ),

Check warning on line 32 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Missing $domain parameter in function call to __(). If this text string is supposed to use a WP Core translation, use the "default" text domain.
'6.6.0'
);
return false;
}

if ( preg_match( '/[A-Z]+/', $template_name ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Block template names must not contain uppercase characters.' ),

Check warning on line 41 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Missing $domain parameter in function call to __(). If this text string is supposed to use a WP Core translation, use the "default" text domain.
'6.6.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' ),

Check warning on line 51 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Missing $domain parameter in function call to __(). If this text string is supposed to use a WP Core translation, use the "default" text domain.
'6.6.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.' ), $template_name ),

Check warning on line 61 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Missing $domain parameter in function call to __(). If this text string is supposed to use a WP Core translation, use the "default" text domain.
'6.6.0'
);
return false;
}

if ( ! $template ) {
$theme_name = get_stylesheet();

Check warning on line 68 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 15 spaces but found 1 space
$template = new WP_Block_Template();

Check warning on line 69 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 17 spaces but found 1 space
$template->id = $theme_name . '//' . $args['slug'];
$template->theme = $theme_name; // @todo If not attached to the theme, this should be the plugin URI.
$template->plugin = array_key_exists( 'plugin', $args ) ? $args[ 'plugin' ] : '';

Check failure on line 72 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array keys must NOT be surrounded by spaces if they only contain a string or an integer.
$template->author = null;
$template->content = array_key_exists( 'path', $args ) ? file_get_contents( $args[ 'path' ] ) : '';

Check failure on line 74 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array keys must NOT be surrounded by spaces if they only contain a string or an integer.
$template->source = 'plugin';
$template->slug = array_key_exists( 'slug', $args ) ? $args['slug'] : '';
$template->type = $template_type;
$template->title = array_key_exists( 'title', $args ) ? $args['title'] : '';
$template->description = array_key_exists( 'description', $args ) ? $args['description'] : '';
$template->status = 'publish';
$template->has_theme_file = true;
$template->origin = 'plugin';
$template->is_custom = false;
$template->post_types = array_key_exists( 'post_types', $args ) ? $args['post_types'] : '';
$template->area = $template_type === 'wp_template_part' && array_key_exists( 'area', $args ) ? $args['area'] : '';

Check failure on line 85 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Use Yoda Condition checks, you must.
}

$this->registered_block_templates[ $template_type ][ $template_name ] = $template;

return $template;
}

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_name => $template ) {

Check failure on line 100 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Space after opening control structure is required

Check failure on line 100 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

No space before opening parenthesis is prohibited

Check failure on line 100 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Expected 1 space after FOREACH keyword; 0 found
if ( $template->slug === $template_slug ) {
return $template;
}
}

return null;
}

/**
* Retrieves a registered template.
*
* @since 6.6.0
*
* @param string $template_type Template type, either `wp_template` or `wp_template_part`.
* @param string $template_name Block type name including namespace.
* @return WP_Block_Type|null The registered block type, 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`.' ),
'6.6.0'
);
return false;
}

if ( ! $this->is_registered( $template_type, $template_name ) ) {
return null;
}

return $this->registered_block_templates[ $template_type ][ $template_name ];
}

/**
* Retrieves all registered block templates by type.
*
* @since 6.6.0
*
* @param string $template_type Template type, either `wp_template` or `wp_template_part`.
* @return WP_Block_Template[] Associative array of `$block_type_name => $block_type` 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`.' ),
'6.6.0'
);
return false;
}

return $this->registered_block_templates[ $template_type ];
}

/**
* Retrieves all registered block templates by type.
*
* @since 6.6.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 ) {

Check failure on line 183 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Space after opening control structure is required

Check failure on line 183 in lib/class-wp-block-templates-registry.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

No space before opening parenthesis is prohibited
if ( ! empty( $slugs_to_include ) && ! in_array( $template->slug, $slugs_to_include ) ) {
unset( $all_templates[ $template_name ] );
}

if ( ! empty( $slugs_to_skip ) && in_array( $template->slug, $slugs_to_skip ) ) {
unset( $all_templates[ $template_name ] );
}

if ( 'wp_template_part' === $template_type && $template->area !== $area ) {
unset( $all_templates[ $template_name ] );
}

if ( ! empty( $post_type ) && ! in_array( $post_type, $template->post_types ) ) {
unset( $all_templates[ $template_name ] );
}
}

return $all_templates;
}

/**
* Checks if a block template is registered.
*
* @since 6.6.0
*
* @param string $template_type Template type, either `wp_template` or `wp_template_part`.
* @param string $template_name Block type 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`.' ),
'6.6.0'
);
return false;
}

return isset( $this->registered_block_templates[ $template_type ][ $template_name ] );
}

public function unregister( $template_name ) {
// @todo
}

/**
* Utility method to retrieve the main instance of the class.
*
* The instance will be created if it does not exist yet.
*
* @since 6.6.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;
}
}
4 changes: 3 additions & 1 deletion lib/compat/wordpress-6.5/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ function _gutenberg_get_wp_templates_author_text_field( $template_object ) {
return empty( $theme_name ) ? $template_object['theme'] : $theme_name;
case 'plugin':
$plugins = get_plugins();
$plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object['theme'] . '.php' ) ) ];
// $plugin_name = array_key_exists('plugin', $template_object ) ? $template_object['plugin'] : $template_object['theme'];
$plugin_name = array_key_exists( 'plugin', $template_object ) ? $template_object['plugin'] . '/' . $template_object['plugin'] : 'woocommerce/woocommerce';
$plugin = $plugins[ plugin_basename( sanitize_text_field( $plugin_name . '.php' ) ) ];
return empty( $plugin['Name'] ) ? $template_object['theme'] : $plugin['Name'];
case 'site':
return get_bloginfo( 'name' );
Expand Down
14 changes: 14 additions & 0 deletions lib/compat/wordpress-6.6/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,20 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t
foreach ( $template_files as $template_file ) {
$query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
}

/*
* 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 );
$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 );
}

/**
Expand Down
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,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';
Expand Down
6 changes: 4 additions & 2 deletions packages/edit-site/src/utils/is-template-removable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ 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 ) {
return false;
}

return (
template.source === TEMPLATE_ORIGINS.custom && ! template.has_theme_file
template.source === TEMPLATE_ORIGINS.custom &&
template.origin !== 'plugin' && // @todo check `editor_visiblity` value here.
! template.has_theme_file
);
}
3 changes: 2 additions & 1 deletion packages/edit-site/src/utils/is-template-revertable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
}
2 changes: 1 addition & 1 deletion packages/editor/src/store/private-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } );
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/store/utils/is-template-revertable.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,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 */
}

0 comments on commit 748e203

Please sign in to comment.