Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
61 changes: 61 additions & 0 deletions src/wp-includes/block-supports/auto-register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Auto-register block support.
*
* @package WordPress
* @since 7.0.0
*/

/**
* Marks user-defined attributes for auto-generated inspector controls.
*
* This filter runs during block type registration, before the WP_Block_Type
* is instantiated. Block supports add their attributes AFTER the block type
* is created (via {@see WP_Block_Supports::register_attributes()}), so any attributes
* present at this stage are user-defined.
*
* The marker tells generateFieldsFromAttributes() which attributes should
* get auto-generated inspector controls. Attributes are excluded if they:
* - Have a 'source' (HTML-derived, edited inline not via inspector)
* - Have role 'local' (internal state, not user-configurable)
* - Have an unsupported type (only 'string', 'number', 'integer', 'boolean' are supported)
* - Were added by block supports (added after this filter runs)
*
* @since 7.0.0
* @access private
*
* @param array<string, mixed> $args Array of arguments for registering a block type.
* @return array<string, mixed> Modified block type arguments.
*/
function wp_mark_auto_generate_control_attributes( array $args ): array {
if ( empty( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
return $args;
}

$has_auto_register = ! empty( $args['supports']['autoRegister'] );
if ( ! $has_auto_register ) {
return $args;
}

foreach ( $args['attributes'] as $attr_key => $attr_schema ) {
// Skip HTML-derived attributes (edited inline, not via inspector).
if ( ! empty( $attr_schema['source'] ) ) {
continue;
}
// Skip internal attributes (not user-configurable).
if ( isset( $attr_schema['role'] ) && 'local' === $attr_schema['role'] ) {
continue;
}
// Skip unsupported types (only 'string', 'number', 'integer', 'boolean' are supported).
$type = $attr_schema['type'] ?? null;
if ( ! in_array( $type, array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
continue;
}
$args['attributes'][ $attr_key ]['autoGenerateControl'] = true;
}

return $args;
}

// Priority 5 to mark original attributes before other filters (priority 10+) might add their own.
add_filter( 'register_block_type_args', 'wp_mark_auto_generate_control_attributes', 5 );
28 changes: 28 additions & 0 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -3131,3 +3131,31 @@ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) {
}
return $arg;
}

/**
* Exposes blocks with autoRegister flag for ServerSideRender in the editor.
*
* Detects blocks that have the autoRegister flag set in their supports
* and passes them to JavaScript for auto-registration with ServerSideRender.
*
* @access private
* @since 7.0.0
*/
function _wp_enqueue_auto_register_blocks() {
$auto_register_blocks = array();
$registered_blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();

foreach ( $registered_blocks as $block_name => $block_type ) {
if ( ! empty( $block_type->supports['autoRegister'] ) && ! empty( $block_type->render_callback ) ) {
$auto_register_blocks[] = $block_name;
}
}

if ( ! empty( $auto_register_blocks ) ) {
wp_add_inline_script(
'wp-block-library',
sprintf( 'window.__unstableAutoRegisterBlocks = %s;', wp_json_encode( $auto_register_blocks ) ),
'before'
);
}
}
1 change: 1 addition & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@
add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets' );
add_action( 'enqueue_block_editor_assets', 'wp_enqueue_block_editor_script_modules' );
add_action( 'enqueue_block_editor_assets', 'wp_enqueue_global_styles_css_custom_properties' );
add_action( 'enqueue_block_editor_assets', '_wp_enqueue_auto_register_blocks' );
add_action( 'wp_print_scripts', 'wp_just_in_time_script_localization' );
add_filter( 'print_scripts_array', 'wp_prototype_before_jquery' );
add_action( 'customize_controls_print_styles', 'wp_resource_hints', 1 );
Expand Down
1 change: 1 addition & 0 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@
require ABSPATH . WPINC . '/class-wp-block-supports.php';
require ABSPATH . WPINC . '/block-supports/utils.php';
require ABSPATH . WPINC . '/block-supports/align.php';
require ABSPATH . WPINC . '/block-supports/auto-register.php';
require ABSPATH . WPINC . '/block-supports/custom-classname.php';
require ABSPATH . WPINC . '/block-supports/generated-classname.php';
require ABSPATH . WPINC . '/block-supports/settings.php';
Expand Down
144 changes: 144 additions & 0 deletions tests/phpunit/tests/block-supports/auto-register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
/**
* @group block-supports
*
* @covers ::wp_mark_auto_generate_control_attributes
*/
class Tests_Block_Supports_Auto_Register extends WP_UnitTestCase {

/**
* Tests that attributes are marked when autoRegister is enabled.
*
* @ticket 64639
*/
public function test_marks_attributes_with_auto_register_flag() {
$settings = array(
'supports' => array( 'autoRegister' => true ),
'attributes' => array(
'title' => array( 'type' => 'string' ),
'count' => array( 'type' => 'integer' ),
),
);

$result = wp_mark_auto_generate_control_attributes( $settings );

$this->assertTrue( $result['attributes']['title']['autoGenerateControl'] );
$this->assertTrue( $result['attributes']['count']['autoGenerateControl'] );
}

/**
* Tests that attributes are not marked without autoRegister flag.
*
* @ticket 64639
*/
public function test_does_not_mark_attributes_without_auto_register() {
$settings = array(
'attributes' => array(
'title' => array( 'type' => 'string' ),
),
);

$result = wp_mark_auto_generate_control_attributes( $settings );

$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['title'] );
}

/**
* Tests that attributes with source are excluded.
*
* @ticket 64639
*/
public function test_excludes_attributes_with_source() {
$settings = array(
'supports' => array( 'autoRegister' => true ),
'attributes' => array(
'title' => array( 'type' => 'string' ),
'content' => array(
'type' => 'string',
'source' => 'html',
),
),
);

$result = wp_mark_auto_generate_control_attributes( $settings );

$this->assertTrue( $result['attributes']['title']['autoGenerateControl'] );
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['content'] );
}

/**
* Tests that attributes with role: local are excluded.
*
* Example: The 'blob' attribute in media blocks (image, video, file, audio)
* stores a temporary blob URL during file upload. This is internal state
* that shouldn't be shown in the inspector or saved to the database.
*
* @ticket 64639
*/
public function test_excludes_attributes_with_role_local() {
$settings = array(
'supports' => array( 'autoRegister' => true ),
'attributes' => array(
'title' => array( 'type' => 'string' ),
'blob' => array(
'type' => 'string',
'role' => 'local',
),
),
);

$result = wp_mark_auto_generate_control_attributes( $settings );

$this->assertTrue( $result['attributes']['title']['autoGenerateControl'] );
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['blob'] );
}

/**
* Tests that empty attributes are handled gracefully.
*
* @ticket 64639
*/
public function test_handles_empty_attributes() {
$settings = array(
'supports' => array( 'autoRegister' => true ),
);

$result = wp_mark_auto_generate_control_attributes( $settings );

$this->assertSame( $settings, $result );
}

/**
* Tests that only allowed attributes are marked.
*
* @ticket 64639
*/
public function test_excludes_unsupported_types() {
$settings = array(
'supports' => array( 'autoRegister' => true ),
'attributes' => array(
// Supported types
'text' => array( 'type' => 'string' ),
'price' => array( 'type' => 'number' ),
'count' => array( 'type' => 'integer' ),
'enabled' => array( 'type' => 'boolean' ),
// Unsupported types
'metadata' => array( 'type' => 'object' ),
'items' => array( 'type' => 'array' ),
'config' => array( 'type' => 'null' ),
'unknown' => array( 'type' => 'unknown' ),
),
);

$result = wp_mark_auto_generate_control_attributes( $settings );

$this->assertTrue( $result['attributes']['text']['autoGenerateControl'] );
$this->assertTrue( $result['attributes']['price']['autoGenerateControl'] );
$this->assertTrue( $result['attributes']['count']['autoGenerateControl'] );
$this->assertTrue( $result['attributes']['enabled']['autoGenerateControl'] );
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['metadata'] );
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['items'] );
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['config'] );
$this->assertArrayNotHasKey( 'autoGenerateControl', $result['attributes']['unknown'] );
}
}
Loading