Make WordPress Core

Changeset 59073


Ignore:
Timestamp:
09/20/2024 02:05:50 AM (3 weeks ago)
Author:
noisysocks
Message:

Editor: Add plugin template registration API and improve theme overrides for plugin-registered templates

This commit introduces a new API to allow plugins to easily register block
templates with wp_register_block_template() and the
WP_Block_Templates_Registry class, addressing the complexity of hooking into
multiple filters. It also ensures plugin-registered templates overridden by
themes fall back to the plugin-provided title and description when the theme
doesn't define them.

See https://github.com/WordPress/gutenberg/pull/61577.
See https://github.com/WordPress/gutenberg/pull/64610.

Fixes #61804.
Props aljullu, peterwilsoncc, antonvlasenko, azaozz, youknowriad, noisysocks.

Location:
trunk
Files:
2 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-template-utils.php

    r59003 r59073  
    592592    $template->is_custom      = true;
    593593    $template->modified       = null;
     594
     595    if ( 'wp_template' === $template_type ) {
     596        $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_file['slug'] );
     597        if ( $registered_template ) {
     598            $template->plugin      = $registered_template->plugin;
     599            $template->title       = empty( $template->title ) || $template->title === $template->slug ? $registered_template->title : $template->title;
     600            $template->description = empty( $template->description ) ? $registered_template->description : $template->description;
     601        }
     602    }
    594603
    595604    if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
     
    10151024    }
    10161025
     1026    if ( 'wp_template' === $post->post_type ) {
     1027        $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug );
     1028        if ( $registered_template ) {
     1029            $template->plugin      = $registered_template->plugin;
     1030            $template->origin      =
     1031                'theme' !== $template->origin && 'theme' !== $template->source ?
     1032                'plugin' :
     1033                $template->origin;
     1034            $template->title       = empty( $template->title ) || $template->title === $template->slug ? $registered_template->title : $template->title;
     1035            $template->description = empty( $template->description ) ? $registered_template->description : $template->description;
     1036        }
     1037    }
     1038
    10171039    $hooked_blocks = get_hooked_blocks();
    10181040    if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
     
    11581180            $query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
    11591181        }
     1182
     1183        if ( 'wp_template' === $template_type ) {
     1184            // Add templates registered in the template registry. Filtering out the ones which have a theme file.
     1185            $registered_templates          = WP_Block_Templates_Registry::get_instance()->get_by_query( $query );
     1186            $matching_registered_templates = array_filter(
     1187                $registered_templates,
     1188                function ( $registered_template ) use ( $template_files ) {
     1189                    foreach ( $template_files as $template_file ) {
     1190                        if ( $template_file['slug'] === $registered_template->slug ) {
     1191                            return false;
     1192                        }
     1193                    }
     1194                    return true;
     1195                }
     1196            );
     1197            $query_result                  = array_merge( $query_result, $matching_registered_templates );
     1198        }
    11601199    }
    11611200
     
    12881327    list( $theme, $slug ) = $parts;
    12891328
    1290     if ( get_stylesheet() !== $theme ) {
    1291         /** This filter is documented in wp-includes/block-template-utils.php */
    1292         return apply_filters( 'get_block_file_template', null, $id, $template_type );
    1293     }
    1294 
    1295     $template_file = _get_block_template_file( $template_type, $slug );
    1296     if ( null === $template_file ) {
    1297         /** This filter is documented in wp-includes/block-template-utils.php */
    1298         return apply_filters( 'get_block_file_template', null, $id, $template_type );
    1299     }
    1300 
    1301     $block_template = _build_block_template_result_from_file( $template_file, $template_type );
     1329    if ( get_stylesheet() === $theme ) {
     1330        $template_file = _get_block_template_file( $template_type, $slug );
     1331        if ( null !== $template_file ) {
     1332            $block_template = _build_block_template_result_from_file( $template_file, $template_type );
     1333
     1334            /** This filter is documented in wp-includes/block-template-utils.php */
     1335            return apply_filters( 'get_block_file_template', $block_template, $id, $template_type );
     1336        }
     1337    }
     1338
     1339    $block_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug );
    13021340
    13031341    /**
     
    16661704        }
    16671705
    1668         $content = get_comment_delimited_block_content(
     1706        $content               = get_comment_delimited_block_content(
    16691707            'core/template-part',
    16701708            $attributes,
    16711709            $changes->post_content
    16721710        );
    1673         $content = apply_block_hooks_to_content( $content, $template, 'set_ignored_hooked_blocks_metadata' );
     1711        $content               = apply_block_hooks_to_content( $content, $template, 'set_ignored_hooked_blocks_metadata' );
    16741712        $changes->post_content = remove_serialized_parent_block( $content );
    16751713
  • trunk/src/wp-includes/block-template.php

    r57785 r59073  
    359359    }
    360360}
     361
     362/**
     363 * Register a block template.
     364 *
     365 * @since 6.7.0
     366 *
     367 * @param string       $template_name  Template name in the form of `plugin_uri//template_name`.
     368 * @param array|string $args           {
     369 *     @type string        $title                 Optional. Title of the template as it will be shown in the Site Editor
     370 *                                                and other UI elements.
     371 *     @type string        $description           Optional. Description of the template as it will be shown in the Site
     372 *                                                Editor.
     373 *     @type string        $content               Optional. Default content of the template that will be used when the
     374 *                                                template is rendered or edited in the editor.
     375 *     @type string[]      $post_types            Optional. Array of post types to which the template should be available.
     376 *     @type string        $plugin                Optional. Slug of the plugin that registers the template.
     377 * }
     378 * @return WP_Block_Template|WP_Error The registered template object on success, WP_Error object on failure.
     379 */
     380function wp_register_block_template( $template_name, $args = array() ) {
     381    return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args );
     382}
     383
     384/**
     385 * Unregister a block template.
     386 *
     387 * @since 6.7.0
     388 *
     389 * @param string $template_name Template name in the form of `plugin_uri//template_name`.
     390 * @return WP_Block_Template|WP_Error The unregistered template object on success, WP_Error object on failure or if the
     391 *                                    template doesn't exist.
     392 */
     393function wp_unregister_block_template( $template_name ) {
     394    return WP_Block_Templates_Registry::get_instance()->unregister( $template_name );
     395}
  • trunk/src/wp-includes/class-wp-block-template.php

    r55992 r59073  
    133133
    134134    /**
     135     * Plugin.
     136     *
     137     * @since 6.7.0
     138     * @var string|null
     139     */
     140    public $plugin;
     141
     142    /**
    135143     * Post types.
    136144     *
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php

    r58707 r59073  
    327327     */
    328328    public function get_item( $request ) {
    329         if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
     329        if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) {
    330330            $template = get_block_file_template( $request['id'], $this->post_type );
    331331        } else {
     
    777777        }
    778778
     779        if ( rest_is_field_included( 'plugin', $fields ) ) {
     780            $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug );
     781            if ( $registered_template ) {
     782                $data['plugin'] = $registered_template->plugin;
     783            }
     784        }
     785
    779786        $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
    780787        $data    = $this->add_additional_fields_to_object( $data, $request );
     
    832839
    833840            // Added by plugin.
    834             if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) {
     841            if ( 'plugin' === $template_object->origin ) {
    835842                return 'plugin';
    836843            }
     
    866873                return empty( $theme_name ) ? $template_object->theme : $theme_name;
    867874            case 'plugin':
    868                 $plugins = get_plugins();
    869                 $plugin  = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ];
    870                 return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name'];
     875                if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'get_plugin_data' ) ) {
     876                    require_once ABSPATH . 'wp-admin/includes/plugin.php';
     877                }
     878                if ( isset( $template_object->plugin ) ) {
     879                    $plugins = wp_get_active_and_valid_plugins();
     880
     881                    foreach ( $plugins as $plugin_file ) {
     882                        $plugin_basename = plugin_basename( $plugin_file );
     883                        // Split basename by '/' to get the plugin slug.
     884                        list( $plugin_slug, ) = explode( '/', $plugin_basename );
     885
     886                        if ( $plugin_slug === $template_object->plugin ) {
     887                            $plugin_data = get_plugin_data( $plugin_file );
     888
     889                            if ( ! empty( $plugin_data['Name'] ) ) {
     890                                return $plugin_data['Name'];
     891                            }
     892
     893                            break;
     894                        }
     895                    }
     896                }
     897
     898                /*
     899                 * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
     900                 * compatibility with templates that were registered before the plugin attribute was added.
     901                 */
     902                $plugins         = get_plugins();
     903                $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) );
     904                if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
     905                    return $plugins[ $plugin_basename ]['Name'];
     906                }
     907                return isset( $template_object->plugin ) ?
     908                    $template_object->plugin :
     909                    $template_object->theme;
    871910            case 'site':
    872911                return get_bloginfo( 'name' );
     
    11351174                'readonly'    => true,
    11361175            );
     1176            $schema['properties']['plugin']    = array(
     1177                'type'        => 'string',
     1178                'description' => __( 'Plugin that registered the template.' ),
     1179                'readonly'    => true,
     1180                'context'     => array( 'view', 'edit', 'embed' ),
     1181            );
    11371182        }
    11381183
  • trunk/src/wp-settings.php

    r58925 r59073  
    193193require ABSPATH . WPINC . '/global-styles-and-settings.php';
    194194require ABSPATH . WPINC . '/class-wp-block-template.php';
     195require ABSPATH . WPINC . '/class-wp-block-templates-registry.php';
    195196require ABSPATH . WPINC . '/block-template-utils.php';
    196197require ABSPATH . WPINC . '/block-template.php';
  • trunk/tests/phpunit/tests/block-template.php

    r59061 r59073  
    435435
    436436    /**
     437     * Tests that get_block_templates() returns plugin-registered templates.
     438     *
     439     * @ticket 61804
     440     *
     441     * @covers ::get_block_templates
     442     */
     443    public function test_get_block_templates_from_registry() {
     444        $template_name = 'test-plugin//test-template';
     445
     446        wp_register_block_template( $template_name );
     447
     448        $templates = get_block_templates();
     449
     450        $this->assertArrayHasKey( $template_name, $templates );
     451
     452        wp_unregister_block_template( $template_name );
     453    }
     454
     455    /**
     456     * Tests that get_block_template() returns plugin-registered templates.
     457     *
     458     * @ticket 61804
     459     *
     460     * @covers ::get_block_template
     461     */
     462    public function test_get_block_template_from_registry() {
     463        $template_name = 'test-plugin//test-template';
     464        $args          = array(
     465            'title' => 'Test Template',
     466        );
     467
     468        wp_register_block_template( $template_name, $args );
     469
     470        $template = get_block_template( 'block-theme//test-template' );
     471
     472        $this->assertSame( 'Test Template', $template->title );
     473
     474        wp_unregister_block_template( $template_name );
     475    }
     476
     477    /**
    437478     * Registers a test block to log `in_the_loop()` results.
    438479     *
  • trunk/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php

    r57710 r59073  
    311311        $properties = $data['schema']['properties'];
    312312
    313         $this->assertCount( 18, $properties );
     313        $this->assertCount( 19, $properties );
    314314        $this->assertArrayHasKey( 'id', $properties, 'ID key should exist in properties.' );
    315315        $this->assertArrayHasKey( 'slug', $properties, 'Slug key should exist in properties.' );
     
    329329        $this->assertArrayHasKey( 'author_text', $properties, 'author_text key should exist in properties.' );
    330330        $this->assertArrayHasKey( 'original_source', $properties, 'original_source key should exist in properties.' );
     331        $this->assertArrayHasKey( 'plugin', $properties, 'plugin key should exist in properties.' );
    331332    }
    332333
  • trunk/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php

    r57710 r59073  
    450450        $properties = $data['schema']['properties'];
    451451
    452         $this->assertCount( 18, $properties );
     452        $this->assertCount( 19, $properties );
    453453        $this->assertArrayHasKey( 'id', $properties, 'ID key should exist in properties.' );
    454454        $this->assertArrayHasKey( 'slug', $properties, 'Slug key should exist in properties.' );
     
    468468        $this->assertArrayHasKey( 'author_text', $properties, 'author_text key should exist in properties.' );
    469469        $this->assertArrayHasKey( 'original_source', $properties, 'original_source key should exist in properties.' );
     470        $this->assertArrayHasKey( 'plugin', $properties, 'plugin key should exist in properties.' );
    470471    }
    471472
  • trunk/tests/phpunit/tests/rest-api/wpRestTemplatesController.php

    r58227 r59073  
    530530
    531531    /**
     532     * Tests that get_item() returns plugin-registered templates.
     533     *
     534     * @ticket 61804
     535     *
     536     * @covers WP_REST_Templates_Controller::get_item
     537     */
     538    public function test_get_item_from_registry() {
     539        wp_set_current_user( self::$admin_id );
     540
     541        $template_name = 'test-plugin//test-template';
     542        $args          = array(
     543            'content'     => 'Template content',
     544            'title'       => 'Test Template',
     545            'description' => 'Description of test template',
     546            'post_types'  => array( 'post', 'page' ),
     547        );
     548
     549        wp_register_block_template( $template_name, $args );
     550
     551        $request  = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' );
     552        $response = rest_get_server()->dispatch( $request );
     553
     554        $this->assertNotWPError( $response, "Fetching a registered template shouldn't cause an error." );
     555
     556        $data = $response->get_data();
     557
     558        $this->assertSame( 'default//test-template', $data['id'], 'Template ID mismatch.' );
     559        $this->assertSame( 'default', $data['theme'], 'Template theme mismatch.' );
     560        $this->assertSame( 'Template content', $data['content']['raw'], 'Template content mismatch.' );
     561        $this->assertSame( 'test-template', $data['slug'], 'Template slug mismatch.' );
     562        $this->assertSame( 'plugin', $data['source'], "Template source should be 'plugin'." );
     563        $this->assertSame( 'plugin', $data['origin'], "Template origin should be 'plugin'." );
     564        $this->assertSame( 'test-plugin', $data['author_text'], 'Template author text mismatch.' );
     565        $this->assertSame( 'Description of test template', $data['description'], 'Template description mismatch.' );
     566        $this->assertSame( 'Test Template', $data['title']['rendered'], 'Template title mismatch.' );
     567        $this->assertSame( 'test-plugin', $data['plugin'], 'Plugin name mismatch.' );
     568
     569        wp_unregister_block_template( $template_name );
     570
     571        $request  = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' );
     572        $response = rest_get_server()->dispatch( $request );
     573
     574        $this->assertNotWPError( $response, "Fetching an unregistered template shouldn't cause an error." );
     575        $this->assertSame( 404, $response->get_status(), 'Fetching an unregistered template should return 404.' );
     576    }
     577
     578    /**
    532579     * @ticket 54507
    533580     * @dataProvider data_sanitize_template_id
     
    864911        $data       = $response->get_data();
    865912        $properties = $data['schema']['properties'];
    866         $this->assertCount( 17, $properties );
     913        $this->assertCount( 18, $properties );
    867914        $this->assertArrayHasKey( 'id', $properties );
    868915        $this->assertArrayHasKey( 'description', $properties );
     
    883930        $this->assertArrayHasKey( 'author_text', $properties );
    884931        $this->assertArrayHasKey( 'original_source', $properties );
     932        $this->assertArrayHasKey( 'plugin', $properties );
    885933    }
    886934
Note: See TracChangeset for help on using the changeset viewer.