WordPress.org

Make WordPress Core

Changeset 48141


Ignore:
Timestamp:
06/23/2020 03:43:19 PM (12 days ago)
Author:
gziolo
Message:

Editor: Introduce new API method that register block from block.json metadata file

Backports changes added to Gutenberg in:

register_block_type_from_metadata function is going to be used to register all blocks on the server using block.json metadata files.

Props ocean90, azaozz, aduth, mcsf, jorgefilipecosta, spacedmonkey, nosolosw, swissspidy and noahtallen.
Fixes #50263.

Location:
trunk
Files:
5 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r48102 r48141  
    3939function unregister_block_type( $name ) {
    4040    return WP_Block_Type_Registry::get_instance()->unregister( $name );
     41}
     42
     43/**
     44 * Removes the block asset's path prefix if provided.
     45 *
     46 * @since 5.5.0
     47 *
     48 * @param string $asset_handle_or_path Asset handle or prefixed path.
     49 * @return string Path without the prefix or the original value.
     50 */
     51function remove_block_asset_path_prefix( $asset_handle_or_path ) {
     52    $path_prefix = 'file:';
     53    if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) {
     54        return $asset_handle_or_path;
     55    }
     56    return substr(
     57        $asset_handle_or_path,
     58        strlen( $path_prefix )
     59    );
     60}
     61
     62/**
     63 * Generates the name for an asset based on the name of the block
     64 * and the field name provided.
     65 *
     66 * @since 5.5.0
     67 *
     68 * @param string $block_name Name of the block.
     69 * @param string $field_name Name of the metadata field.
     70 * @return string Generated asset name for the block's field.
     71 */
     72function generate_block_asset_handle( $block_name, $field_name ) {
     73    $field_mappings = array(
     74        'editorScript' => 'editor-script',
     75        'script'       => 'script',
     76        'editorStyle'  => 'editor-style',
     77        'style'        => 'style',
     78    );
     79    return str_replace( '/', '-', $block_name ) .
     80        '-' . $field_mappings[ $field_name ];
     81}
     82
     83/**
     84 * Finds a script handle for the selected block metadata field. It detects
     85 * when a path to file was provided and finds a corresponding
     86 * asset file with details necessary to register the script under
     87 * automatically generated handle name. It returns unprocessed script handle
     88 * otherwise.
     89 *
     90 * @since 5.5.0
     91 *
     92 * @param array  $metadata   Block metadata.
     93 * @param string $field_name Field name to pick from metadata.
     94 * @return string|bool Script handle provided directly or created through
     95 *                     script's registration, or false on failure.
     96 */
     97function register_block_script_handle( $metadata, $field_name ) {
     98    if ( empty( $metadata[ $field_name ] ) ) {
     99        return false;
     100    }
     101    $script_handle = $metadata[ $field_name ];
     102    $script_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
     103    if ( $script_handle === $script_path ) {
     104        return $script_handle;
     105    }
     106
     107    $script_handle     = generate_block_asset_handle( $metadata['name'], $field_name );
     108    $script_asset_path = realpath(
     109        dirname( $metadata['file'] ) . '/' .
     110        substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
     111    );
     112    if ( ! file_exists( $script_asset_path ) ) {
     113        $message = sprintf(
     114            /* translators: %1: field name. %2: block name */
     115            __( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ),
     116            $field_name,
     117            $metadata['name']
     118        );
     119        _doing_it_wrong( __FUNCTION__, $message, '5.5.0' );
     120        return false;
     121    }
     122    $script_asset = require $script_asset_path;
     123    $result       = wp_register_script(
     124        $script_handle,
     125        plugins_url( $script_path, $metadata['file'] ),
     126        $script_asset['dependencies'],
     127        $script_asset['version']
     128    );
     129    return $result ? $script_handle : false;
     130}
     131
     132/**
     133 * Finds a style handle for the block metadata field. It detects when a path
     134 * to file was provided and registers the style under automatically
     135 * generated handle name. It returns unprocessed style handle otherwise.
     136 *
     137 * @since 5.5.0
     138 *
     139 * @param array  $metadata Block metadata.
     140 * @param string $field_name Field name to pick from metadata.
     141 * @return string|boolean Style handle provided directly or created through
     142 *                        style's registration, or false on failure.
     143 */
     144function register_block_style_handle( $metadata, $field_name ) {
     145    if ( empty( $metadata[ $field_name ] ) ) {
     146        return false;
     147    }
     148    $style_handle = $metadata[ $field_name ];
     149    $style_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
     150    if ( $style_handle === $style_path ) {
     151        return $style_handle;
     152    }
     153
     154    $style_handle = generate_block_asset_handle( $metadata['name'], $field_name );
     155    $block_dir    = dirname( $metadata['file'] );
     156    $result       = wp_register_style(
     157        $style_handle,
     158        plugins_url( $style_path, $metadata['file'] ),
     159        array(),
     160        filemtime( realpath( "$block_dir/$style_path" ) )
     161    );
     162    return $result ? $style_handle : false;
     163}
     164
     165/**
     166 * Registers a block type from metadata stored in the `block.json` file.
     167 *
     168 * @since 5.5.0
     169 *
     170 * @param string $file_or_folder Path to the JSON file with metadata definition for
     171 *                               the block or path to the folder where the `block.json` file is located.
     172 * @param array  $args {
     173 *     Optional. Array of block type arguments. Any arguments may be defined, however the
     174 *     ones described below are supported by default. Default empty array.
     175 *
     176 *     @type callable $render_callback Callback used to render blocks of this block type.
     177 * }
     178 * @return WP_Block_Type|false The registered block type on success, or false on failure.
     179 */
     180function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
     181    $filename      = 'block.json';
     182    $metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
     183        trailingslashit( $file_or_folder ) . $filename :
     184        $file_or_folder;
     185    if ( ! file_exists( $metadata_file ) ) {
     186        return false;
     187    }
     188
     189    $metadata = json_decode( file_get_contents( $metadata_file ), true );
     190    if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
     191        return false;
     192    }
     193    $metadata['file'] = $metadata_file;
     194
     195    $settings          = array();
     196    $property_mappings = array(
     197        'title'           => 'title',
     198        'category'        => 'category',
     199        'parent'          => 'parent',
     200        'icon'            => 'icon',
     201        'description'     => 'description',
     202        'keywords'        => 'keywords',
     203        'attributes'      => 'attributes',
     204        'providesContext' => 'provides_context',
     205        'usesContext'     => 'uses_context',
     206        'supports'        => 'supports',
     207        'styles'          => 'styles',
     208        'example'         => 'example',
     209    );
     210
     211    foreach ( $property_mappings as $key => $mapped_key ) {
     212        if ( isset( $metadata[ $key ] ) ) {
     213            $settings[ $mapped_key ] = $metadata[ $key ];
     214        }
     215    }
     216
     217    if ( ! empty( $metadata['editorScript'] ) ) {
     218        $settings['editor_script'] = register_block_script_handle(
     219            $metadata,
     220            'editorScript'
     221        );
     222    }
     223
     224    if ( ! empty( $metadata['script'] ) ) {
     225        $settings['script'] = register_block_script_handle(
     226            $metadata,
     227            'script'
     228        );
     229    }
     230
     231    if ( ! empty( $metadata['editorStyle'] ) ) {
     232        $settings['editor_style'] = register_block_style_handle(
     233            $metadata,
     234            'editorStyle'
     235        );
     236    }
     237
     238    if ( ! empty( $metadata['style'] ) ) {
     239        $settings['style'] = register_block_style_handle(
     240            $metadata,
     241            'style'
     242        );
     243    }
     244
     245    return register_block_type(
     246        $metadata['name'],
     247        array_merge(
     248            $settings,
     249            $args
     250        )
     251    );
    41252}
    42253
  • trunk/tests/phpunit/tests/blocks/register.php

    r46586 r48141  
    104104
    105105    /**
     106     * @ticket 50263
     107     */
     108    function test_does_not_remove_block_asset_path_prefix() {
     109        $result = remove_block_asset_path_prefix( 'script-handle' );
     110
     111        $this->assertSame( 'script-handle', $result );
     112    }
     113
     114    /**
     115     * @ticket 50263
     116     */
     117    function test_removes_block_asset_path_prefix() {
     118        $result = remove_block_asset_path_prefix( 'file:./block.js' );
     119
     120        $this->assertSame( './block.js', $result );
     121    }
     122
     123    /**
     124     * @ticket 50263
     125     */
     126    function test_generate_block_asset_handle() {
     127        $block_name = 'unit-tests/my-block';
     128
     129        $this->assertSame(
     130            'unit-tests-my-block-editor-script',
     131            generate_block_asset_handle( $block_name, 'editorScript' )
     132        );
     133        $this->assertSame(
     134            'unit-tests-my-block-script',
     135            generate_block_asset_handle( $block_name, 'script' )
     136        );
     137        $this->assertSame(
     138            'unit-tests-my-block-editor-style',
     139            generate_block_asset_handle( $block_name, 'editorStyle' )
     140        );
     141        $this->assertSame(
     142            'unit-tests-my-block-style',
     143            generate_block_asset_handle( $block_name, 'style' )
     144        );
     145    }
     146
     147    /**
     148     * @ticket 50263
     149     */
     150    function test_field_not_found_register_block_script_handle() {
     151        $result = register_block_script_handle( array(), 'script' );
     152
     153        $this->assertFalse( $result );
     154    }
     155
     156    /**
     157     * @ticket 50263
     158     */
     159    function test_empty_value_register_block_script_handle() {
     160        $metadata = array( 'script' => '' );
     161        $result   = register_block_script_handle( $metadata, 'script' );
     162
     163        $this->assertFalse( $result );
     164    }
     165
     166    /**
     167     * @expectedIncorrectUsage register_block_script_handle
     168     * @ticket 50263
     169     */
     170    function test_missing_asset_file_register_block_script_handle() {
     171        $metadata = array(
     172            'file'   => __FILE__,
     173            'name'   => 'unit-tests/test-block',
     174            'script' => 'file:./fixtures/missing-asset.js',
     175        );
     176        $result   = register_block_script_handle( $metadata, 'script' );
     177
     178        $this->assertFalse( $result );
     179    }
     180
     181    /**
     182     * @ticket 50263
     183     */
     184    function test_handle_passed_register_block_script_handle() {
     185        $metadata = array(
     186            'editorScript' => 'test-script-handle',
     187        );
     188        $result   = register_block_script_handle( $metadata, 'editorScript' );
     189
     190        $this->assertSame( 'test-script-handle', $result );
     191    }
     192
     193    /**
     194     * @ticket 50263
     195     */
     196    function test_success_register_block_script_handle() {
     197        $metadata = array(
     198            'file'   => __FILE__,
     199            'name'   => 'unit-tests/test-block',
     200            'script' => 'file:./fixtures/block.js',
     201        );
     202        $result   = register_block_script_handle( $metadata, 'script' );
     203
     204        $this->assertSame( 'unit-tests-test-block-script', $result );
     205    }
     206
     207    /**
     208     * @ticket 50263
     209     */
     210    function test_field_not_found_register_block_style_handle() {
     211        $result = register_block_style_handle( array(), 'style' );
     212
     213        $this->assertFalse( $result );
     214    }
     215
     216    /**
     217     * @ticket 50263
     218     */
     219    function test_empty_value_found_register_block_style_handle() {
     220        $metadata = array( 'style' => '' );
     221        $result   = register_block_style_handle( $metadata, 'style' );
     222
     223        $this->assertFalse( $result );
     224    }
     225
     226    /**
     227     * @ticket 50263
     228     */
     229    function test_handle_passed_register_block_style_handle() {
     230        $metadata = array(
     231            'style' => 'test-style-handle',
     232        );
     233        $result   = register_block_style_handle( $metadata, 'style' );
     234
     235        $this->assertSame( 'test-style-handle', $result );
     236    }
     237
     238    /**
     239     * @ticket 50263
     240     */
     241    function test_success_register_block_style_handle() {
     242        $metadata = array(
     243            'file'  => __FILE__,
     244            'name'  => 'unit-tests/test-block',
     245            'style' => 'file:./fixtures/block.css',
     246        );
     247        $result   = register_block_style_handle( $metadata, 'style' );
     248
     249        $this->assertSame( 'unit-tests-test-block-style', $result );
     250    }
     251
     252    /**
     253     * Tests that the function returns false when the `block.json` is not found
     254     * in the WordPress core.
     255     *
     256     * @ticket 50263
     257     */
     258    function test_metadata_not_found_in_wordpress_core() {
     259        $result = register_block_type_from_metadata( 'unknown' );
     260
     261        $this->assertFalse( $result );
     262    }
     263
     264    /**
     265     * Tests that the function returns false when the `block.json` is not found
     266     * in the current directory.
     267     *
     268     * @ticket 50263
     269     */
     270    function test_metadata_not_found_in_the_current_directory() {
     271        $result = register_block_type_from_metadata( __DIR__ );
     272
     273        $this->assertFalse( $result );
     274    }
     275
     276    /**
     277     * Tests that the function returns the registered block when the `block.json`
     278     * is found in the fixtures directory.
     279     *
     280     * @ticket 50263
     281     */
     282    function test_block_registers_with_metadata_fixture() {
     283        $result = register_block_type_from_metadata(
     284            __DIR__ . '/fixtures'
     285        );
     286
     287        $this->assertInstanceOf( 'WP_Block_Type', $result );
     288        $this->assertSame( 'my-plugin/notice', $result->name );
     289        $this->assertSame( 'Notice', $result->title );
     290        $this->assertSame( 'common', $result->category );
     291        $this->assertEqualSets( array( 'core/group' ), $result->parent );
     292        $this->assertSame( 'star', $result->icon );
     293        $this->assertSame( 'Shows warning, error or success notices…', $result->description );
     294        $this->assertEqualSets( array( 'alert', 'message' ), $result->keywords );
     295        $this->assertEquals(
     296            array(
     297                'message' => array(
     298                    'type'     => 'string',
     299                    'source'   => 'html',
     300                    'selector' => '.message',
     301                ),
     302            ),
     303            $result->attributes
     304        );
     305        $this->assertEquals(
     306            array(
     307                'my-plugin/message' => 'message',
     308            ),
     309            $result->provides_context
     310        );
     311        $this->assertEqualSets( array( 'groupId' ), $result->uses_context );
     312        $this->assertEquals(
     313            array(
     314                'align'             => true,
     315                'lightBlockWrapper' => true,
     316            ),
     317            $result->supports
     318        );
     319        $this->assertEquals(
     320            array(
     321                array(
     322                    'name'      => 'default',
     323                    'label'     => 'Default',
     324                    'isDefault' => true,
     325                ),
     326                array(
     327                    'name'  => 'other',
     328                    'label' => 'Other',
     329                ),
     330            ),
     331            $result->styles
     332        );
     333        $this->assertEquals(
     334            array(
     335                'attributes' => array(
     336                    'message' => 'This is a notice!',
     337                ),
     338            ),
     339            $result->example
     340        );
     341        $this->assertSame( 'my-plugin-notice-editor-script', $result->editor_script );
     342        $this->assertSame( 'my-plugin-notice-script', $result->script );
     343        $this->assertSame( 'my-plugin-notice-editor-style', $result->editor_style );
     344        $this->assertSame( 'my-plugin-notice-style', $result->style );
     345    }
     346
     347    /**
    106348     * @ticket 45109
    107349     */
Note: See TracChangeset for help on using the changeset viewer.