Skip to content

Commit

Permalink
block.json: Allow passing filename as variations field (#62092)
Browse files Browse the repository at this point in the history
Allow passing the name of a PHP file that returns a block's variations in the `block.json` file's `variations` field.

Co-authored-by: ockham <bernhard-reiter@git.wordpress.org>
Co-authored-by: tjcafferkey <tomjcafferkey@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
  • Loading branch information
4 people authored Jul 9, 2024
1 parent a58c1f9 commit 4c93cf2
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 81 deletions.
3 changes: 3 additions & 0 deletions backport-changelog/6.7/6668.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/6668

* https://github.com/WordPress/gutenberg/pull/62092
44 changes: 43 additions & 1 deletion docs/reference-guides/block-api/block-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ See the [Example documentation](/docs/reference-guides/block-api/block-registrat

### Variations

- Type: `object[]`
- Type: `object[]|WPDefinedPath` ([learn more](#wpdefinedpath))
- Optional
- Localized: Yes (`title`, `description`, and `keywords` of each variation only)
- Property: `variations`
Expand All @@ -454,6 +454,48 @@ Block Variations is the API that allows a block to have similar versions of it,

_Note: In JavaScript you can provide a function for the `isActive` property, and a React element for the `icon`. In the `block.json` file both only support strings_

Starting with version 6.7, it is possible to specify a PHP file in `block.json` that generates the list of block variations on the server side:

```json
{ "variations": "file:./variations.php" }
```

That PHP file is expected to `return` an array that contains the block variations. Strings found in the variations returned from the PHP file will not be localized automatically; instead, use the `__()` function as usual.

For example:

```php
<?php
// Generate variations for a Social Icon kind of block.

return array(
array(
'isDefault' => true,
'name' => 'wordpress',
'title' => 'WordPress',
'icon' => 'wordpress',
'attributes' => array(
'service' => 'wordpress',
),
'isActive' => array( 'service' )
),
array(
'name' => 'mail',
'title' => __( 'Mail' ),
'keywords' => array(
__( 'email' ),
__( 'e-mail' )
),
'icon' => 'mail',
'attributes' => array(
'service' => 'mail',
),
'isActive' => array( 'mail' )
),
);

```

See [the variations documentation](/docs/reference-guides/block-api/block-variations.md) for more details.

### Block Hooks
Expand Down
45 changes: 45 additions & 0 deletions lib/compat/wordpress-6.7/blocks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
/**
* Temporary compatibility shims for block APIs present in Gutenberg.
*
* @package gutenberg
*/

/**
* Allow passing a PHP file as `variations` field for Core versions < 6.7
*
* @param array $settings Array of determined settings for registering a block type.
* @param array $metadata Metadata provided for registering a block type.
* @return array The block type settings
*/
function gutenberg_filter_block_type_metadata_settings_allow_variations_php_file( $settings, $metadata ) {
// If `variations` is a string, it's the name of a PHP file that
// generates the variations.
if ( ! empty( $settings['variations'] ) && is_string( $settings['variations'] ) ) {
$variations_path = wp_normalize_path(
realpath(
dirname( $metadata['file'] ) . '/' .
remove_block_asset_path_prefix( $settings['variations'] )
)
);
if ( $variations_path ) {
/**
* Generates the list of block variations.
*
* @since 6.7.0
*
* @return string Returns the list of block variations.
*/
$settings['variation_callback'] = static function () use ( $variations_path ) {
$variations = require $variations_path;
return $variations;
};
// The block instance's `variations` field is only allowed to be an array
// (of known block variations). We unset it so that the block instance will
// provide a getter that returns the result of the `variation_callback` instead.
unset( $settings['variations'] );
}
}
return $settings;
}
add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_allow_variations_php_file', 10, 2 );
3 changes: 3 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.6/option.php';
require __DIR__ . '/compat/wordpress-6.6/post.php';

// WordPress 6.7 compat.
require __DIR__ . '/compat/wordpress-6.7/blocks.php';

// Experimental features.
require __DIR__ . '/experimental/block-editor-settings-mobile.php';
require __DIR__ . '/experimental/blocks.php';
Expand Down
9 changes: 7 additions & 2 deletions packages/blocks/src/store/process-block-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,14 @@ export const processBlockType =
save: () => null,
...bootstrappedBlockType,
...blockSettings,
// blockType.variations can be defined as a filePath.
variations: mergeBlockVariations(
bootstrappedBlockType?.variations,
blockSettings?.variations
Array.isArray( bootstrappedBlockType?.variations )
? bootstrappedBlockType.variations
: [],
Array.isArray( blockSettings?.variations )
? blockSettings.variations
: []
),
};

Expand Down
48 changes: 48 additions & 0 deletions phpunit/blocks/block-json-variations-filename-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Test for the block.json's variations field being a PHP file.
*
* @package WordPress
* @subpackage Blocks
*/

/**
* Test that block variations can be registered from a PHP file.
*
* @group blocks
*/
class Block_Json_Variations_Filename_Test extends WP_UnitTestCase {
/**
* Tear down each test method.
*/
public function tear_down() {
$registry = WP_Block_Type_Registry::get_instance();

if ( $registry->is_registered( 'my-plugin/notice' ) ) {
$registry->unregister( 'my-plugin/notice' );
}

parent::tear_down();
}

/**
* Tests registering a block with variations from a PHP file.
*
* @covers ::register_block_type_from_metadata
*/
public function test_register_block_type_from_metadata_with_variations_php_file() {
$filter_metadata_registration = static function ( $metadata ) {
$metadata['variations'] = 'file:./variations.php';
return $metadata;
};

add_filter( 'block_type_metadata', $filter_metadata_registration, 10, 2 );
$result = register_block_type_from_metadata( GUTENBERG_DIR_TESTFIXTURES );
remove_filter( 'block_type_metadata', $filter_metadata_registration );

$this->assertInstanceOf( 'WP_Block_Type', $result, 'The block was not registered' );

$expected_variations = require GUTENBERG_DIR_TESTFIXTURES . '/variations.php';
$this->assertSame( $expected_variations, $result->variations, "Block variations haven't been set correctly." );
}
}
10 changes: 10 additions & 0 deletions phpunit/fixtures/variations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

return array(
array(
'name' => 'warning',
'title' => 'warning',
'description' => 'Shows warning.',
'keywords' => array( 'warning' ),
),
);
165 changes: 87 additions & 78 deletions schemas/json/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -897,91 +897,100 @@
]
},
"variations": {
"type": "array",
"description": "Block Variations is the API that allows a block to have similar versions of it, but all these versions share some common functionality.",
"items": {
"type": "object",
"required": [ "name", "title" ],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "The unique and machine-readable name."
},
"title": {
"type": "string",
"description": "A human-readable variation title."
},
"description": {
"type": "string",
"description": "A detailed variation description."
},
"category": {
"description": "A category classification, used in search interfaces to arrange block types by category.",
"anyOf": [
{
"type": "string"
"oneOf": [
{
"type": "string",
"description": "The path to a PHP file that returns an array of block variations."
},
{
"type": "array",
"description": "An array of block variations.",
"items": {
"type": "object",
"required": [ "name", "title" ],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "The unique and machine-readable name."
},
{
"enum": [
"text",
"media",
"design",
"widgets",
"theme",
"embed"
"title": {
"type": "string",
"description": "A human-readable variation title."
},
"description": {
"type": "string",
"description": "A detailed variation description."
},
"category": {
"description": "A category classification, used in search interfaces to arrange block types by category.",
"anyOf": [
{
"type": "string"
},
{
"enum": [
"text",
"media",
"design",
"widgets",
"theme",
"embed"
]
}
]
},
"icon": {
"description": "An icon helping to visualize the variation. It can have the same shape as the block type.",
"type": "string"
},
"isDefault": {
"type": "boolean",
"default": false,
"description": "Indicates whether the current variation is the default one."
},
"attributes": {
"type": "object",
"description": "Values that override block attributes."
},
"innerBlocks": {
"type": "array",
"items": {
"type": "array"
},
"description": "Initial configuration of nested blocks."
},
"example": {
"type": "object",
"description": "Example provides structured data for the block preview. You can set to undefined to disable the preview shown for the block type."
},
"scope": {
"type": "array",
"description": "The list of scopes where the variation is applicable.",
"items": {
"enum": [ "inserter", "block", "transform" ]
},
"default": [ "inserter", "block" ]
},
"keywords": {
"type": "array",
"description": "An array of terms (which can be translated) that help users discover the variation while searching.",
"items": {
"type": "string"
}
},
"isActive": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of attributes that should be compared. Each attributes will be matched and the variation will be active if all of them are matching."
}
]
},
"icon": {
"description": "An icon helping to visualize the variation. It can have the same shape as the block type.",
"type": "string"
},
"isDefault": {
"type": "boolean",
"default": false,
"description": "Indicates whether the current variation is the default one."
},
"attributes": {
"type": "object",
"description": "Values that override block attributes."
},
"innerBlocks": {
"type": "array",
"items": {
"type": "array"
},
"description": "Initial configuration of nested blocks."
},
"example": {
"type": "object",
"description": "Example provides structured data for the block preview. You can set to undefined to disable the preview shown for the block type."
},
"scope": {
"type": "array",
"description": "The list of scopes where the variation is applicable.",
"items": {
"enum": [ "inserter", "block", "transform" ]
},
"default": [ "inserter", "block" ]
},
"keywords": {
"type": "array",
"description": "An array of terms (which can be translated) that help users discover the variation while searching.",
"items": {
"type": "string"
}
},
"isActive": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of attributes that should be compared. Each attributes will be matched and the variation will be active if all of them are matching."
}
}
}
]
},
"render": {
"type": "string",
Expand Down

0 comments on commit 4c93cf2

Please sign in to comment.