Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

block.json: Allow passing filename as variations field #62092

Merged
merged 17 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -141,6 +141,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' ),
),
);
163 changes: 85 additions & 78 deletions schemas/json/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -897,91 +897,98 @@
]
},
"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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it is worth adding a description property here to better document the expected usage of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point. I thought it might collide with the description a couple lines above (i.e. which applies to both oneOf options), but it seems to be allowed by the JSON schema spec, so I'll add it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
{
"type": "array",
"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
Loading