Make WordPress Core

Changeset 57026


Ignore:
Timestamp:
10/28/2023 01:00:14 AM (4 months ago)
Author:
SergeyBiryukov
Message:

Blocks: Parse the arguments earlier in register_block_type_from_metadata().

This makes it possible to register a block by passing an array of arguments, without the presence of a block.json file.

Follow-up to [48141], [49948].

Props aristath, spacedmonkey, mukesh27, costdev, audrasjb, oglekler, felipeelia, hellofromTonya.
Fixes #56865.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r56970 r57026  
    353353
    354354    $is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC );
    355 
    356     if ( ! $is_core_block && ! file_exists( $metadata_file ) ) {
     355    // If the block is not a core block, the metadata file must exist.
     356    $metadata_file_exists = $is_core_block || file_exists( $metadata_file );
     357    if ( ! $metadata_file_exists && empty( $args['name'] ) ) {
    357358        return false;
    358359    }
    359360
    360361    // Try to get metadata from the static cache for core blocks.
    361     $metadata = false;
     362    $metadata = array();
    362363    if ( $is_core_block ) {
    363364        $core_block_name = str_replace( ABSPATH . WPINC . '/blocks/', '', $file_or_folder );
     
    368369
    369370    // If metadata is not found in the static cache, read it from the file.
    370     if ( ! $metadata ) {
     371    if ( $metadata_file_exists && empty( $metadata ) ) {
    371372        $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
    372373    }
    373374
    374     if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
     375    if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
    375376        return false;
    376377    }
    377     $metadata['file'] = wp_normalize_path( realpath( $metadata_file ) );
     378
     379    $metadata['file'] = $metadata_file_exists ? wp_normalize_path( realpath( $metadata_file ) ) : null;
    378380
    379381    /**
     
    405407    $property_mappings = array(
    406408        'apiVersion'      => 'api_version',
     409        'name'            => 'name',
    407410        'title'           => 'title',
    408411        'category'        => 'category',
     
    427430        if ( isset( $metadata[ $key ] ) ) {
    428431            $settings[ $mapped_key ] = $metadata[ $key ];
    429             if ( $textdomain && isset( $i18n_schema->$key ) ) {
     432            if ( $metadata_file_exists && $textdomain && isset( $i18n_schema->$key ) ) {
    430433                $settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain );
    431434            }
    432         }
    433     }
    434 
    435     $script_fields = array(
    436         'editorScript' => 'editor_script_handles',
    437         'script'       => 'script_handles',
    438         'viewScript'   => 'view_script_handles',
    439     );
    440     foreach ( $script_fields as $metadata_field_name => $settings_field_name ) {
    441         if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
    442             $scripts           = $metadata[ $metadata_field_name ];
    443             $processed_scripts = array();
    444             if ( is_array( $scripts ) ) {
    445                 for ( $index = 0; $index < count( $scripts ); $index++ ) {
    446                     $result = register_block_script_handle(
    447                         $metadata,
    448                         $metadata_field_name,
    449                         $index
    450                     );
    451                     if ( $result ) {
    452                         $processed_scripts[] = $result;
    453                     }
    454                 }
    455             } else {
    456                 $result = register_block_script_handle(
    457                     $metadata,
    458                     $metadata_field_name
    459                 );
    460                 if ( $result ) {
    461                     $processed_scripts[] = $result;
    462                 }
    463             }
    464             $settings[ $settings_field_name ] = $processed_scripts;
    465         }
    466     }
    467 
    468     $style_fields = array(
    469         'editorStyle' => 'editor_style_handles',
    470         'style'       => 'style_handles',
    471     );
    472     foreach ( $style_fields as $metadata_field_name => $settings_field_name ) {
    473         if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
    474             $styles           = $metadata[ $metadata_field_name ];
    475             $processed_styles = array();
    476             if ( is_array( $styles ) ) {
    477                 for ( $index = 0; $index < count( $styles ); $index++ ) {
    478                     $result = register_block_style_handle(
    479                         $metadata,
    480                         $metadata_field_name,
    481                         $index
    482                     );
    483                     if ( $result ) {
    484                         $processed_styles[] = $result;
    485                     }
    486                 }
    487             } else {
    488                 $result = register_block_style_handle(
    489                     $metadata,
    490                     $metadata_field_name
    491                 );
    492                 if ( $result ) {
    493                     $processed_styles[] = $result;
    494                 }
    495             }
    496             $settings[ $settings_field_name ] = $processed_styles;
    497         }
    498     }
    499 
    500     if ( ! empty( $metadata['blockHooks'] ) ) {
    501         /**
    502          * Map camelCased position string (from block.json) to snake_cased block type position.
    503          *
    504          * @var array
    505          */
    506         $position_mappings = array(
    507             'before'     => 'before',
    508             'after'      => 'after',
    509             'firstChild' => 'first_child',
    510             'lastChild'  => 'last_child',
    511         );
    512 
    513         $settings['block_hooks'] = array();
    514         foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) {
    515             // Avoid infinite recursion (hooking to itself).
    516             if ( $metadata['name'] === $anchor_block_name ) {
    517                 _doing_it_wrong(
    518                     __METHOD__,
    519                     __( 'Cannot hook block to itself.' ),
    520                     '6.4.0'
    521                 );
    522                 continue;
    523             }
    524 
    525             if ( ! isset( $position_mappings[ $position ] ) ) {
    526                 continue;
    527             }
    528 
    529             $settings['block_hooks'][ $anchor_block_name ] = $position_mappings[ $position ];
    530435        }
    531436    }
     
    558463    }
    559464
     465    $settings = array_merge( $settings, $args );
     466
     467    $script_fields = array(
     468        'editorScript' => 'editor_script_handles',
     469        'script'       => 'script_handles',
     470        'viewScript'   => 'view_script_handles',
     471    );
     472    foreach ( $script_fields as $metadata_field_name => $settings_field_name ) {
     473        if ( ! empty( $settings[ $metadata_field_name ] ) ) {
     474            $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
     475        }
     476        if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
     477            $scripts           = $metadata[ $metadata_field_name ];
     478            $processed_scripts = array();
     479            if ( is_array( $scripts ) ) {
     480                for ( $index = 0; $index < count( $scripts ); $index++ ) {
     481                    $result = register_block_script_handle(
     482                        $metadata,
     483                        $metadata_field_name,
     484                        $index
     485                    );
     486                    if ( $result ) {
     487                        $processed_scripts[] = $result;
     488                    }
     489                }
     490            } else {
     491                $result = register_block_script_handle(
     492                    $metadata,
     493                    $metadata_field_name
     494                );
     495                if ( $result ) {
     496                    $processed_scripts[] = $result;
     497                }
     498            }
     499            $settings[ $settings_field_name ] = $processed_scripts;
     500        }
     501    }
     502
     503    $style_fields = array(
     504        'editorStyle' => 'editor_style_handles',
     505        'style'       => 'style_handles',
     506    );
     507    foreach ( $style_fields as $metadata_field_name => $settings_field_name ) {
     508        if ( ! empty( $settings[ $metadata_field_name ] ) ) {
     509            $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
     510        }
     511        if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
     512            $styles           = $metadata[ $metadata_field_name ];
     513            $processed_styles = array();
     514            if ( is_array( $styles ) ) {
     515                for ( $index = 0; $index < count( $styles ); $index++ ) {
     516                    $result = register_block_style_handle(
     517                        $metadata,
     518                        $metadata_field_name,
     519                        $index
     520                    );
     521                    if ( $result ) {
     522                        $processed_styles[] = $result;
     523                    }
     524                }
     525            } else {
     526                $result = register_block_style_handle(
     527                    $metadata,
     528                    $metadata_field_name
     529                );
     530                if ( $result ) {
     531                    $processed_styles[] = $result;
     532                }
     533            }
     534            $settings[ $settings_field_name ] = $processed_styles;
     535        }
     536    }
     537
     538    if ( ! empty( $metadata['blockHooks'] ) ) {
     539        /**
     540         * Map camelCased position string (from block.json) to snake_cased block type position.
     541         *
     542         * @var array
     543         */
     544        $position_mappings = array(
     545            'before'     => 'before',
     546            'after'      => 'after',
     547            'firstChild' => 'first_child',
     548            'lastChild'  => 'last_child',
     549        );
     550
     551        $settings['block_hooks'] = array();
     552        foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) {
     553            // Avoid infinite recursion (hooking to itself).
     554            if ( $metadata['name'] === $anchor_block_name ) {
     555                _doing_it_wrong(
     556                    __METHOD__,
     557                    __( 'Cannot hook block to itself.' ),
     558                    '6.4.0'
     559                );
     560                continue;
     561            }
     562
     563            if ( ! isset( $position_mappings[ $position ] ) ) {
     564                continue;
     565            }
     566
     567            $settings['block_hooks'][ $anchor_block_name ] = $position_mappings[ $position ];
     568        }
     569    }
     570
    560571    /**
    561572     * Filters the settings determined from the block type metadata.
     
    566577     * @param array $metadata Metadata provided for registering a block type.
    567578     */
    568     $settings = apply_filters(
    569         'block_type_metadata_settings',
    570         array_merge(
    571             $settings,
    572             $args
    573         ),
    574         $metadata
    575     );
     579    $settings = apply_filters( 'block_type_metadata_settings', $settings, $metadata );
     580
     581    $metadata['name'] = ! empty( $settings['name'] ) ? $settings['name'] : $metadata['name'];
    576582
    577583    return WP_Block_Type_Registry::get_instance()->register(
  • trunk/tests/phpunit/tests/blocks/register.php

    r56607 r57026  
    601601
    602602    /**
     603     * Tests registering a block using arguments instead of a block.json file.
     604     *
     605     * @ticket 56865
     606     *
     607     * @covers ::register_block_type_from_metadata
     608     */
     609    public function test_register_block_type_from_metadata_with_arguments() {
     610        $result = register_block_type_from_metadata(
     611            '',
     612            array(
     613                'api_version' => 2,
     614                'name'        => 'tests/notice-from-array',
     615                'title'       => 'Notice from array',
     616                'category'    => 'common',
     617                'icon'        => 'star',
     618                'description' => 'Shows warning, error or success notices… (registered from an array)',
     619                'keywords'    => array(
     620                    'alert',
     621                    'message',
     622                ),
     623                'textdomain'  => 'notice-from-array',
     624            )
     625        );
     626
     627        $this->assertInstanceOf( 'WP_Block_Type', $result, 'The block was not registered' );
     628        $this->assertSame( 2, $result->api_version, 'The API version is incorrect' );
     629        $this->assertSame( 'tests/notice-from-array', $result->name, 'The block name is incorrect' );
     630        $this->assertSame( 'Notice from array', $result->title, 'The block title is incorrect' );
     631        $this->assertSame( 'common', $result->category, 'The block category is incorrect' );
     632        $this->assertSame( 'star', $result->icon, 'The block icon is incorrect' );
     633        $this->assertSame(
     634            'Shows warning, error or success notices… (registered from an array)',
     635            $result->description,
     636            'The block description is incorrect'
     637        );
     638        $this->assertSameSets( array( 'alert', 'message' ), $result->keywords, 'The block keywords are incorrect' );
     639    }
     640
     641    /**
     642     * Tests that defined $args can properly override the block.json file.
     643     *
     644     * @ticket 56865
     645     *
     646     * @covers ::register_block_type_from_metadata
     647     */
     648    public function test_block_registers_with_args_override() {
     649        $result = register_block_type_from_metadata(
     650            DIR_TESTDATA . '/blocks/notice',
     651            array(
     652                'name'  => 'tests/notice-with-overrides',
     653                'title' => 'Overriden title',
     654                'style' => array( 'tests-notice-style-overridden' ),
     655            )
     656        );
     657
     658        $this->assertInstanceOf( 'WP_Block_Type', $result, 'The block was not registered' );
     659        $this->assertSame( 2, $result->api_version, 'The API version is incorrect' );
     660        $this->assertSame( 'tests/notice-with-overrides', $result->name, 'The block name was not overridden' );
     661        $this->assertSame( 'Overriden title', $result->title, 'The block title was not overridden' );
     662        $this->assertSameSets(
     663            array( 'tests-notice-editor-script' ),
     664            $result->editor_script_handles,
     665            'The block editor script is incorrect'
     666        );
     667        $this->assertSameSets(
     668            array( 'tests-notice-style-overridden' ),
     669            $result->style_handles,
     670            'The block style was not overridden'
     671        );
     672        $this->assertIsCallable( $result->render_callback );
     673    }
     674
     675    /**
     676     * Tests that when the `name` is missing, `register_block_type_from_metadata()`
     677     * will return `false`.
     678     *
     679     * @ticket 56865
     680     *
     681     * @covers ::register_block_type_from_metadata
     682     *
     683     * @dataProvider data_register_block_registers_with_args_override_returns_false_when_name_is_missing
     684     *
     685     * @param string $file The metadata file.
     686     * @param array  $args Array of block type arguments.
     687     */
     688    public function test_block_registers_with_args_override_returns_false_when_name_is_missing( $file, $args ) {
     689        $this->assertFalse( register_block_type_from_metadata( $file, $args ) );
     690    }
     691
     692    /**
     693     * Data provider.
     694     *
     695     * @return array[]
     696     */
     697    public function data_register_block_registers_with_args_override_returns_false_when_name_is_missing() {
     698        return array(
     699            'no block.json file and no name argument' => array(
     700                'file' => '', // No block.json file.
     701                'args' => array(
     702                    'title' => 'Overriden title',
     703                    'style' => array( 'tests-notice-style-overridden' ),
     704                ),
     705            ),
     706            'existing file and args not an array'     => array(
     707                // A file that exists but is empty. This will bypass the file_exists() check.
     708                'file' => DIR_TESTDATA . '/blocks/notice/block.js',
     709                'args' => false,
     710            ),
     711            'existing file and args[name] missing'    => array(
     712                // A file that exists but is empty. This will bypass the file_exists() check.
     713                'file' => DIR_TESTDATA . '/blocks/notice/block.js',
     714                'args' => array(
     715                    'title' => 'Overriden title',
     716                    'style' => array( 'tests-notice-style-overridden' ),
     717                ),
     718            ),
     719        );
     720    }
     721
     722    /**
    603723     * Tests that the function returns the registered block when the `block.json`
    604724     * is found in the fixtures directory.
Note: See TracChangeset for help on using the changeset viewer.