Skip to content

Commit 8f6d312

Browse files
committed
Block Supports: Add autoRegister support for PHP-only block registration.
Introduces block support for PHP-only block registration, enabling developers to register blocks in PHP without requiring JavaScript registration code. When a block declares `'supports' => array( 'autoRegister' => true )` along with a render callback, it is exposed to the client-side via a JavaScript global variable and registered automatically. Example usage: {{{ register_block_type( 'my-plugin/example', array( 'title' => 'My Example Block', 'attributes' => array( 'title' => array( 'type' => 'string', 'default' => 'Hello World', ), 'count' => array( 'type' => 'integer', 'default' => 5, ), ), 'render_callback' => function ( $attributes ) { return sprintf( '<div %1$s>%2$s: %3$d items</div>', get_block_wrapper_attributes(), esc_html( $attributes['title'] ), $attributes['count'] ); }, 'supports' => array( 'autoRegister' => true, ), ) ); }}} Props mcsf, oandregal, ramonopoly, westonruter, wildworks. Fixes #64639. git-svn-id: https://develop.svn.wordpress.org/trunk@61661 602fd350-edb4-49c9-b593-d223f7449a82
1 parent cf065b7 commit 8f6d312

File tree

5 files changed

+235
-0
lines changed

5 files changed

+235
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
/**
3+
* Auto-register block support.
4+
*
5+
* @package WordPress
6+
* @since 7.0.0
7+
*/
8+
9+
/**
10+
* Marks user-defined attributes for auto-generated inspector controls.
11+
*
12+
* This filter runs during block type registration, before the WP_Block_Type
13+
* is instantiated. Block supports add their attributes AFTER the block type
14+
* is created (via {@see WP_Block_Supports::register_attributes()}), so any attributes
15+
* present at this stage are user-defined.
16+
*
17+
* The marker tells generateFieldsFromAttributes() which attributes should
18+
* get auto-generated inspector controls. Attributes are excluded if they:
19+
* - Have a 'source' (HTML-derived, edited inline not via inspector)
20+
* - Have role 'local' (internal state, not user-configurable)
21+
* - Have an unsupported type (only 'string', 'number', 'integer', 'boolean' are supported)
22+
* - Were added by block supports (added after this filter runs)
23+
*
24+
* @since 7.0.0
25+
* @access private
26+
*
27+
* @param array<string, mixed> $args Array of arguments for registering a block type.
28+
* @return array<string, mixed> Modified block type arguments.
29+
*/
30+
function wp_mark_auto_generate_control_attributes( array $args ): array {
31+
if ( empty( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
32+
return $args;
33+
}
34+
35+
$has_auto_register = ! empty( $args['supports']['autoRegister'] );
36+
if ( ! $has_auto_register ) {
37+
return $args;
38+
}
39+
40+
foreach ( $args['attributes'] as $attr_key => $attr_schema ) {
41+
// Skip HTML-derived attributes (edited inline, not via inspector).
42+
if ( ! empty( $attr_schema['source'] ) ) {
43+
continue;
44+
}
45+
// Skip internal attributes (not user-configurable).
46+
if ( isset( $attr_schema['role'] ) && 'local' === $attr_schema['role'] ) {
47+
continue;
48+
}
49+
// Skip unsupported types (only 'string', 'number', 'integer', 'boolean' are supported).
50+
$type = $attr_schema['type'] ?? null;
51+
if ( ! in_array( $type, array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
52+
continue;
53+
}
54+
$args['attributes'][ $attr_key ]['autoGenerateControl'] = true;
55+
}
56+
57+
return $args;
58+
}
59+
60+
// Priority 5 to mark original attributes before other filters (priority 10+) might add their own.
61+
add_filter( 'register_block_type_args', 'wp_mark_auto_generate_control_attributes', 5 );

src/wp-includes/blocks.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3131,3 +3131,31 @@ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) {
31313131
}
31323132
return $arg;
31333133
}
3134+
3135+
/**
3136+
* Exposes blocks with autoRegister flag for ServerSideRender in the editor.
3137+
*
3138+
* Detects blocks that have the autoRegister flag set in their supports
3139+
* and passes them to JavaScript for auto-registration with ServerSideRender.
3140+
*
3141+
* @access private
3142+
* @since 7.0.0
3143+
*/
3144+
function _wp_enqueue_auto_register_blocks() {
3145+
$auto_register_blocks = array();
3146+
$registered_blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();
3147+
3148+
foreach ( $registered_blocks as $block_name => $block_type ) {
3149+
if ( ! empty( $block_type->supports['autoRegister'] ) && ! empty( $block_type->render_callback ) ) {
3150+
$auto_register_blocks[] = $block_name;
3151+
}
3152+
}
3153+
3154+
if ( ! empty( $auto_register_blocks ) ) {
3155+
wp_add_inline_script(
3156+
'wp-block-library',
3157+
sprintf( 'window.__unstableAutoRegisterBlocks = %s;', wp_json_encode( $auto_register_blocks ) ),
3158+
'before'
3159+
);
3160+
}
3161+
}

src/wp-includes/default-filters.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@
623623
add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets' );
624624
add_action( 'enqueue_block_editor_assets', 'wp_enqueue_block_editor_script_modules' );
625625
add_action( 'enqueue_block_editor_assets', 'wp_enqueue_global_styles_css_custom_properties' );
626+
add_action( 'enqueue_block_editor_assets', '_wp_enqueue_auto_register_blocks' );
626627
add_action( 'wp_print_scripts', 'wp_just_in_time_script_localization' );
627628
add_filter( 'print_scripts_array', 'wp_prototype_before_jquery' );
628629
add_action( 'customize_controls_print_styles', 'wp_resource_hints', 1 );

src/wp-settings.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@
394394
require ABSPATH . WPINC . '/class-wp-block-supports.php';
395395
require ABSPATH . WPINC . '/block-supports/utils.php';
396396
require ABSPATH . WPINC . '/block-supports/align.php';
397+
require ABSPATH . WPINC . '/block-supports/auto-register.php';
397398
require ABSPATH . WPINC . '/block-supports/custom-classname.php';
398399
require ABSPATH . WPINC . '/block-supports/generated-classname.php';
399400
require ABSPATH . WPINC . '/block-supports/settings.php';
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
/**
3+
* @group block-supports
4+
*
5+
* @covers ::wp_mark_auto_generate_control_attributes
6+
*/
7+
class Tests_Block_Supports_Auto_Register extends WP_UnitTestCase {
8+
9+
/**
10+
* Tests that attributes are marked when autoRegister is enabled.
11+
*
12+
* @ticket 64639
13+
*/
14+
public function test_marks_attributes_with_auto_register_flag() {
15+
$settings = array(
16+
'supports' => array( 'autoRegister' => true ),
17+
'attributes' => array(
18+
'title' => array( 'type' => 'string' ),
19+
'count' => array( 'type' => 'integer' ),
20+
),
21+
);
22+
23+
$result = wp_mark_auto_generate_control_attributes( $settings );
24+
25+
$this->assertTrue( $result['attributes']['title']['autoGenerateControl'] );
26+
$this->assertTrue( $result['attributes']['count']['autoGenerateControl'] );
27+
}
28+
29+
/**
30+
* Tests that attributes are not marked without autoRegister flag.
31+
*
32+
* @ticket 64639
33+
*/
34+
public function test_does_not_mark_attributes_without_auto_register() {
35+
$settings = array(
36+
'attributes' => array(
37+
'title' => array( 'type' => 'string' ),
38+
),
39+
);
40+
41+
$result = wp_mark_auto_generate_control_attributes( $settings );
42+
43+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['title'] );
44+
}
45+
46+
/**
47+
* Tests that attributes with source are excluded.
48+
*
49+
* @ticket 64639
50+
*/
51+
public function test_excludes_attributes_with_source() {
52+
$settings = array(
53+
'supports' => array( 'autoRegister' => true ),
54+
'attributes' => array(
55+
'title' => array( 'type' => 'string' ),
56+
'content' => array(
57+
'type' => 'string',
58+
'source' => 'html',
59+
),
60+
),
61+
);
62+
63+
$result = wp_mark_auto_generate_control_attributes( $settings );
64+
65+
$this->assertTrue( $result['attributes']['title']['autoGenerateControl'] );
66+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['content'] );
67+
}
68+
69+
/**
70+
* Tests that attributes with role: local are excluded.
71+
*
72+
* Example: The 'blob' attribute in media blocks (image, video, file, audio)
73+
* stores a temporary blob URL during file upload. This is internal state
74+
* that shouldn't be shown in the inspector or saved to the database.
75+
*
76+
* @ticket 64639
77+
*/
78+
public function test_excludes_attributes_with_role_local() {
79+
$settings = array(
80+
'supports' => array( 'autoRegister' => true ),
81+
'attributes' => array(
82+
'title' => array( 'type' => 'string' ),
83+
'blob' => array(
84+
'type' => 'string',
85+
'role' => 'local',
86+
),
87+
),
88+
);
89+
90+
$result = wp_mark_auto_generate_control_attributes( $settings );
91+
92+
$this->assertTrue( $result['attributes']['title']['autoGenerateControl'] );
93+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['blob'] );
94+
}
95+
96+
/**
97+
* Tests that empty attributes are handled gracefully.
98+
*
99+
* @ticket 64639
100+
*/
101+
public function test_handles_empty_attributes() {
102+
$settings = array(
103+
'supports' => array( 'autoRegister' => true ),
104+
);
105+
106+
$result = wp_mark_auto_generate_control_attributes( $settings );
107+
108+
$this->assertSame( $settings, $result );
109+
}
110+
111+
/**
112+
* Tests that only allowed attributes are marked.
113+
*
114+
* @ticket 64639
115+
*/
116+
public function test_excludes_unsupported_types() {
117+
$settings = array(
118+
'supports' => array( 'autoRegister' => true ),
119+
'attributes' => array(
120+
// Supported types
121+
'text' => array( 'type' => 'string' ),
122+
'price' => array( 'type' => 'number' ),
123+
'count' => array( 'type' => 'integer' ),
124+
'enabled' => array( 'type' => 'boolean' ),
125+
// Unsupported types
126+
'metadata' => array( 'type' => 'object' ),
127+
'items' => array( 'type' => 'array' ),
128+
'config' => array( 'type' => 'null' ),
129+
'unknown' => array( 'type' => 'unknown' ),
130+
),
131+
);
132+
133+
$result = wp_mark_auto_generate_control_attributes( $settings );
134+
135+
$this->assertTrue( $result['attributes']['text']['autoGenerateControl'] );
136+
$this->assertTrue( $result['attributes']['price']['autoGenerateControl'] );
137+
$this->assertTrue( $result['attributes']['count']['autoGenerateControl'] );
138+
$this->assertTrue( $result['attributes']['enabled']['autoGenerateControl'] );
139+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['metadata'] );
140+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['items'] );
141+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['config'] );
142+
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['unknown'] );
143+
}
144+
}

0 commit comments

Comments
 (0)