Skip to content

Commit

Permalink
Editor: Introduce new API method that register block from `block.json…
Browse files Browse the repository at this point in the history
…` metadata file

Backports changes added to Gutenberg in:
- WordPress/gutenberg#20794
- WordPress/gutenberg#22519
`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.



git-svn-id: https://develop.svn.wordpress.org/trunk@48141 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
gziolo committed Jun 23, 2020
1 parent 6408e19 commit 5f6ab44
Show file tree
Hide file tree
Showing 6 changed files with 513 additions and 0 deletions.
211 changes: 211 additions & 0 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,217 @@ function unregister_block_type( $name ) {
return WP_Block_Type_Registry::get_instance()->unregister( $name );
}

/**
* Removes the block asset's path prefix if provided.
*
* @since 5.5.0
*
* @param string $asset_handle_or_path Asset handle or prefixed path.
* @return string Path without the prefix or the original value.
*/
function remove_block_asset_path_prefix( $asset_handle_or_path ) {
$path_prefix = 'file:';
if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) {
return $asset_handle_or_path;
}
return substr(
$asset_handle_or_path,
strlen( $path_prefix )
);
}

/**
* Generates the name for an asset based on the name of the block
* and the field name provided.
*
* @since 5.5.0
*
* @param string $block_name Name of the block.
* @param string $field_name Name of the metadata field.
* @return string Generated asset name for the block's field.
*/
function generate_block_asset_handle( $block_name, $field_name ) {
$field_mappings = array(
'editorScript' => 'editor-script',
'script' => 'script',
'editorStyle' => 'editor-style',
'style' => 'style',
);
return str_replace( '/', '-', $block_name ) .
'-' . $field_mappings[ $field_name ];
}

/**
* Finds a script handle for the selected block metadata field. It detects
* when a path to file was provided and finds a corresponding
* asset file with details necessary to register the script under
* automatically generated handle name. It returns unprocessed script handle
* otherwise.
*
* @since 5.5.0
*
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
* @return string|bool Script handle provided directly or created through
* script's registration, or false on failure.
*/
function register_block_script_handle( $metadata, $field_name ) {
if ( empty( $metadata[ $field_name ] ) ) {
return false;
}
$script_handle = $metadata[ $field_name ];
$script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
if ( $script_handle === $script_path ) {
return $script_handle;
}

$script_handle = generate_block_asset_handle( $metadata['name'], $field_name );
$script_asset_path = realpath(
dirname( $metadata['file'] ) . '/' .
substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
);
if ( ! file_exists( $script_asset_path ) ) {
$message = sprintf(
/* translators: %1: field name. %2: block name */
__( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ),
$field_name,
$metadata['name']
);
_doing_it_wrong( __FUNCTION__, $message, '5.5.0' );
return false;
}
$script_asset = require $script_asset_path;
$result = wp_register_script(
$script_handle,
plugins_url( $script_path, $metadata['file'] ),
$script_asset['dependencies'],
$script_asset['version']
);
return $result ? $script_handle : false;
}

/**
* Finds a style handle for the block metadata field. It detects when a path
* to file was provided and registers the style under automatically
* generated handle name. It returns unprocessed style handle otherwise.
*
* @since 5.5.0
*
* @param array $metadata Block metadata.
* @param string $field_name Field name to pick from metadata.
* @return string|boolean Style handle provided directly or created through
* style's registration, or false on failure.
*/
function register_block_style_handle( $metadata, $field_name ) {
if ( empty( $metadata[ $field_name ] ) ) {
return false;
}
$style_handle = $metadata[ $field_name ];
$style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] );
if ( $style_handle === $style_path ) {
return $style_handle;
}

$style_handle = generate_block_asset_handle( $metadata['name'], $field_name );
$block_dir = dirname( $metadata['file'] );
$result = wp_register_style(
$style_handle,
plugins_url( $style_path, $metadata['file'] ),
array(),
filemtime( realpath( "$block_dir/$style_path" ) )
);
return $result ? $style_handle : false;
}

/**
* Registers a block type from metadata stored in the `block.json` file.
*
* @since 5.5.0
*
* @param string $file_or_folder Path to the JSON file with metadata definition for
* the block or path to the folder where the `block.json` file is located.
* @param array $args {
* Optional. Array of block type arguments. Any arguments may be defined, however the
* ones described below are supported by default. Default empty array.
*
* @type callable $render_callback Callback used to render blocks of this block type.
* }
* @return WP_Block_Type|false The registered block type on success, or false on failure.
*/
function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
$filename = 'block.json';
$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
trailingslashit( $file_or_folder ) . $filename :
$file_or_folder;
if ( ! file_exists( $metadata_file ) ) {
return false;
}

$metadata = json_decode( file_get_contents( $metadata_file ), true );
if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
return false;
}
$metadata['file'] = $metadata_file;

$settings = array();
$property_mappings = array(
'title' => 'title',
'category' => 'category',
'parent' => 'parent',
'icon' => 'icon',
'description' => 'description',
'keywords' => 'keywords',
'attributes' => 'attributes',
'providesContext' => 'provides_context',
'usesContext' => 'uses_context',
'supports' => 'supports',
'styles' => 'styles',
'example' => 'example',
);

foreach ( $property_mappings as $key => $mapped_key ) {
if ( isset( $metadata[ $key ] ) ) {
$settings[ $mapped_key ] = $metadata[ $key ];
}
}

if ( ! empty( $metadata['editorScript'] ) ) {
$settings['editor_script'] = register_block_script_handle(
$metadata,
'editorScript'
);
}

if ( ! empty( $metadata['script'] ) ) {
$settings['script'] = register_block_script_handle(
$metadata,
'script'
);
}

if ( ! empty( $metadata['editorStyle'] ) ) {
$settings['editor_style'] = register_block_style_handle(
$metadata,
'editorStyle'
);
}

if ( ! empty( $metadata['style'] ) ) {
$settings['style'] = register_block_style_handle(
$metadata,
'style'
);
}

return register_block_type(
$metadata['name'],
array_merge(
$settings,
$args
)
);
}

/**
* Determine whether a post or content string has blocks.
*
Expand Down
6 changes: 6 additions & 0 deletions tests/phpunit/tests/blocks/fixtures/block.asset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

return array(
'dependencies' => array(),
'version' => 'test',
);
1 change: 1 addition & 0 deletions tests/phpunit/tests/blocks/fixtures/block.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* Test CSS file */
1 change: 1 addition & 0 deletions tests/phpunit/tests/blocks/fixtures/block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* Test JavaScript file. */
52 changes: 52 additions & 0 deletions tests/phpunit/tests/blocks/fixtures/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "my-plugin/notice",
"title": "Notice",
"category": "common",
"parent": [
"core/group"
],
"providesContext": {
"my-plugin/message": "message"
},
"usesContext": [
"groupId"
],
"icon": "star",
"description": "Shows warning, error or success notices…",
"keywords": [
"alert",
"message"
],
"textDomain": "my-plugin",
"attributes": {
"message": {
"type": "string",
"source": "html",
"selector": ".message"
}
},
"supports": {
"align": true,
"lightBlockWrapper": true
},
"styles": [
{
"name": "default",
"label": "Default",
"isDefault": true
},
{
"name": "other",
"label": "Other"
}
],
"example": {
"attributes": {
"message": "This is a notice!"
}
},
"editorScript": "my-plugin-notice-editor-script",
"script": "my-plugin-notice-script",
"editorStyle": "my-plugin-notice-editor-style",
"style": "my-plugin-notice-style"
}
Loading

0 comments on commit 5f6ab44

Please sign in to comment.