Make WordPress Core

Opened 3 years ago

Closed 3 years ago

Last modified 7 days ago

#60282 closed enhancement (fixed)

Add the Block Bindings API, including the "post_meta" and "pattern" sources

Reported by: czapla Owned by: youknowriad
Priority: normal Milestone: 6.5
Component: Editor Version:
Severity: normal Keywords: has-patch has-unit-tests needs-dev-note
Cc: Focuses:

Description

Introduce the Block Bindings API for WordPress.

This includes 2 sources for block bindings:

  • post meta
  • patterns

Change History (44)

This ticket was mentioned in ​PR #5888 on ​WordPress/wordpress-develop by ​@czapla.


3 years ago
#1

  • Keywords has-unit-tests added

More information about the Block Bindings API: ​https://github.com/WordPress/gutenberg/issues/54536


Trac ticket: https://core.trac.wordpress.org/ticket/60282

This PR introduces the Block Bindings API for WordPress.

The API allows developers to connects block attributes to different sources. In this PR, two such sources are included: "post meta" and "pattern". Attributes connected to sources can have their HTML replaced by values coming from the source in a way defined by the binding.

On a technical level, the way the API works is a 3-step process:

  1. Create a binding between block attributes and a source - _in the editor_.
  2. Get the value from the source defined in the binding - _at runtime._
  3. Update the HTML using the value obtained from the source - _at runtime_.

### 1. Create a binding

The bindings have the following structure:

_An example binding for the content attribute of Paragraph block and the url attribute of the Image block.
Both using the post_meta source._

// These are block attributes
{
    "metadata": {
        "bindings": {
            "content": {
                "source": {
                    "name": "post_meta",
                    "attributes": {
                        "value": "text_custom_field"
                    }
                }
            },
            "url": {
                "source": {
                    "name": "post_meta",
                    "attributes": {
                        "value": "url_custom_field"
                    }
                }
            }
        }
    }
}

This PR does not include any UI for adding those metatada attributes to blocks at the moment. This is intentional as the API is meant as a building block and such UI will be developed later.

### 2. Get the value

This PR includes a mechanism for registering "sources" for the block bindings. A "source" defines where to get a value for the binding from. Two sources are included in this PR:

  • "post meta"
  • "pattern" - which is used by Partially Synced Patterns

Each source is responsible any logic required to obtain the value from a source.

### 3. Update the HTML

At runtime, the HTML API is used to update the HTML of the block with values from the block binding sources. At the moment, a limited and hardcoded number of blocks and attributes is supported in this PR:

$allowed_blocks = array(
    'core/paragraph' => array( 'content' ),
    'core/heading'   => array( 'content' ),
    'core/image'     => array( 'url', 'title', 'alt' ),
    'core/button'    => array( 'url', 'text' ),
);

​@talldanwp commented on ​PR #5888:


3 years ago
#2

The API allows developers to connects block attributes to different sources. In this PR, two such sources are included: "post meta" and "pattern".

An option could be to consider a separate backport PR for the 'pattern' source, as I think there's also a little bit of extra code needed to set up the block context for pattern overrides. Let me know what you think.

​@artemiosans commented on ​PR #5888:


3 years ago
#3

Summarizing my comments, we could further simplify what gets exposed as a public API for v1. We should consider moving two functions to WP_Block class as private methods:

  • wp_block_bindings_replace_html
  • _process_block_bindings

@gziolo Ok I addressed this in ​https://github.com/WordPress/wordpress-develop/pull/5888/commits/9303e02f1c755256fd5011250cd754ed6bf9ccd4. But we still need to expose the processing somehow, right? I added a wrapper function process_bindings to the class so we can still access the logic.

Which leads me to the next observation, that we should follow the established pattern and rename the method to align:

function register_block_binding( $name, $properties )
If we follow other registration helpers, the class name would better fit as:
WP_Block_Bindings_Registry

Ok I've updated the class name and renamed the methods in ​https://github.com/WordPress/wordpress-develop/pull/5888/commits/30f4ed76f41be76c0cb929e883176ad98ec11312. However, I don't think we can go with just register_block_binding because technically, a _binding_ is the act of connecting a block's attribute to a source in the editor, and what we're doing here is registering the source to facilitate that connection. I think it would be good to continue making that distinction to keep things simple to understand.

​@artemiosans commented on ​PR #5888:


3 years ago
#4

Also, the latest changes will break the tests, so we need to update those.

​@artemiosans commented on ​PR #5888:


3 years ago
#5

Hi all! I believe I've addressed all of the outstanding issues up to now except for the following:

  • Refactoring the processing of block bindings into two steps as discussed ​here. I think this one can be handled in a separate PR.
  • Refactoring to use post ID from the block context, which seems it may require more discussion and could be handled separately as well (​discussion 1) (​discussion 2)
  • Potentially remove manual check for supported blocks and properties in the future, as per usage of the HTML API sometime in the future (​discussion)

It seems there's still some discussion to be had on the structure of the bindings object and naming of certain methods as well, namely register() which I can follow up on next week.

Am I missing anything? Let me know what you all think, thanks! 🙏

#7 @gziolo
3 years ago

In 57373:

Editor: Add registry for block binding sources

It is part of the sync from the Gutenberg plugin that introduces the registry for block binding sources required for the new Block Bindings API: ​https://github.com/WordPress/gutenberg/issues/54536.

See #60282.
Props czapla, artemiosans, santosguillamot, sc0ttkclark, lgladdy, talldanwp, swissspidy, youknowriad, fabiankaegy.

​@gziolo commented on ​PR #5888:


3 years ago
#9

I landed the WP_Block_Bindings_Registry class with https://core.trac.wordpress.org/changeset/57373. Let's update the branch to reflect it.

#11 @gziolo
3 years ago

In 57375:

Editor: Add Block Bindings API helpers

It is part of the sync from the Gutenberg plugin that introduces the registry for block binding sources required for the new Block Bindings API: WordPress/gutenberg#54536.

See #60282.
Follow-up [57373].
Props czapla, artemiosans, santosguillamot, sc0ttkclark, lgladdy, talldanwp, swissspidy, youknowriad, fabiankaegy, mukesh27.

​@gziolo commented on ​PR #5888:


3 years ago
#13

I also landed register_block_bindings_source, unregister_block_bindings_source and get_all_registered_block_bindings_sources with functions with https://core.trac.wordpress.org/changeset/57375.

​@czapla commented on ​PR #5888:


3 years ago
#14

There are currently some unit test failures. I'm working on fixing them.

#15 @gziolo
3 years ago

In 57385:

Tests: Remove redundant unregister call in block bindings tear down

Only block bindings sources registered in the tests should get unregistered.

Follow-up for [57375].
See #60282.
Props czapla.

​@czapla commented on ​PR #5888:


3 years ago
#16

~An option could be to consider a separate backport PR for the 'pattern' source, as I think there's also a little bit of extra code needed to set up the block context for pattern overrides. Let me know what you think.~

Actually, this changed recently, there are now no backports for pattern overrides other than the registration of the binding source.

Thanks for the update @talldan 🙏

​@czapla commented on ​PR #5888:


3 years ago
#17

The tests are passing now and I believe all of the feedback has been addressed.

Summary of the updates:


As previosly mentioned by @artemiomorales, the following can be handled in follow-up PRs:

cc @artemiomorales @SantosGuillamot @gziolo

​@artemiosans commented on ​PR #5888:


3 years ago
#18

During my testing, I realized that pattern overrides are no longer working on this branch or on trunk. I haven't been able to look into identifying the cause yet, though it may be getting addressed by this PR on the Gutenberg side:

#19 @swissspidy
3 years ago

  • Component General → Editor

#20 @andraganescu
3 years ago

I opened an issue on Github about ​Standardizing data access for blocks on the server in place of my previous comment.

Last edited 3 years ago by andraganescu (previous) (diff)

​@talldanwp commented on ​PR #5888:


3 years ago
#21

During my testing, I realized that pattern overrides are no longer working on this branch or on trunk. I haven't been able to look into identifying the cause yet, though it may be getting addressed by this PR on the Gutenberg side:

It could be that. A lot of the pattern overrides code also resides in the block library package in gutenberg, so a package update for wordpress core might also be required (and then this branch rebased).

There were quite a few last minute PRs added to the last Gutenberg release.

​@czapla commented on ​PR #5888:


3 years ago
#22

The unit tests are ​failing because Tests_WP_Customize_Widgets::test_get_selective_refreshable_widgets_when_no_theme_supports test calls $this->do_customize_boot_actions(); which calls do_action( 'init' );.

Because of this, the init action is fired twice and ​this check fails.

If understand correctly, init could be fired multiple times per request on a site under some circumstances (which is what happens here). ​Block registration has a similar check.

I'm not sure what the right behavior here should be. I see these options:

  1. For the bindings that ship in core (post-meta & pattern), always unregister any bindings with the same name.
  2. Remove the _doing_it_wrong() altoghether.

What do you think @youknowriad @gziolo ?

​@gziolo commented on ​PR #5888:


3 years ago
#23

The following diff should resolve the issue that exists only in the test env:

  • tests/phpunit/includes/functions.php

    diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php
    index 81d4339db1..0fdff9c71a 100644
    a b tests_add_filter( 'send_auth_cookies', '__return_false' );  
    339339 * @since 5.0.0
    340340 */
    341341function _unhook_block_registration() {
     342        // Block types.
    342343        require __DIR__ . '/unregister-blocks-hooks.php';
    343344        remove_action( 'init', 'register_core_block_types_from_metadata' );
    344345        remove_action( 'init', 'register_block_core_legacy_widget' );
    345346        remove_action( 'init', 'register_block_core_widget_group' );
    346347        remove_action( 'init', 'register_core_block_types_from_metadata' );
     348
     349        // Block binding sources.
     350        remove_action( 'init', '_register_block_bindings_pattern_overrides_source' );
     351        remove_action( 'init', '_register_block_bindings_post_meta_source' );
    347352}
    348353tests_add_filter( 'init', '_unhook_block_registration', 1000 );

It will never be the case in the production code.

#24 @youknowriad
3 years ago

  • Owner set to youknowriad
  • Resolution → fixed
  • Status new → closed

In 57514:

Editor: Add the Block Bindings API.

This introduces the Block Bindings API for WordPress.

The API allows developers to connects block attributes to different sources. In this PR, two such sources are included: "post meta" and "pattern". Attributes connected to sources can have their HTML replaced by values coming from the source in a way defined by the binding.

Props czapla, lgladdy, gziolo, sc0ttkclark, swissspidy, artemiosans, kevin940726, fabiankaegy, santosguillamot, talldanwp, wildworks.
Fixes #60282.

#27 @gziolo
3 years ago

  • Resolution fixed
  • Status closed → reopened

Reopening as there are still issues to fix like the bug discussed in this thread: ​https://github.com/WordPress/wordpress-develop/pull/5888#discussion_r1475692597.

​@youknowriad commented on ​PR #6016:


3 years ago
#28

Thanks for handing the follow-up @gziolo I appreciate it. On a quick glance, everything looks good to me.

#29 @gziolo
3 years ago

In 57526:

Editor: Refactor the way block bindings sources are handled

It fixes the coding style issues reported. It goes further and improves the code quality it other places where the logic for block bindings was added.

Follow-up for [57514].
Props: gziolo, mukesh27, youknowriad, santosguillamot.
See #60282.

This ticket was mentioned in ​PR #6029 on ​WordPress/wordpress-develop by ​@czapla.


3 years ago
#31

This PR improves the inline documentation for the Block Bindings API.

It also implements a minor improvement from ​https://github.com/WordPress/wordpress-develop/pull/5888#discussion_r1476793062. The values passed to $processor->set_attribute() should not be escaped.

​@czapla commented on ​PR #6029:


3 years ago
#32

Maybe it's enough to put there @see register_block_bindings_source annotation. I left two suggestions to discuss further before I commit changes.

That makes sense. I also added a note in 94385a524085363cdd07b9870109123e2601a63c saying it's a low-level method and register_block_bindings_source() should be used in most cases instead.

#33 @gziolo
3 years ago

In 57560:

Editor: Improve code documentation for block bindings

Follow-up [57514].
See #60282.
Props czapla, gziolo, retrofox.

#35 @gziolo
3 years ago

In 57562:

Editor: Introduce WP_Block_Bindings_Source class

Abstracts the block bindings source array into a well-defined object.

Fixes #60447.
See #60282.
Follow-up [57373].
Props czapla, santosguillamot, gziolo.

This ticket was mentioned in ​PR #6059 on ​WordPress/wordpress-develop by ​@czapla.


3 years ago
#36

This PR refactors the processing of block bindings into 2 steps.

#### Before

process_block_bindings() takes the $block_content as input and:

  1. Gets the value for each "bound" attribute from the respective source.
  2. For each updated attribute calls replace_html() to get the updated $block_content filled in with the values from the updated attributes.

#### Now

process_block_bindings() doesn't take any input

  1. Gets the value for each "bound" attribute from the respective source.
  2. Returns the updated attributes with values from the sources.
  3. The updated attributes are used in the render() function of the block where replace_html() is called with the updated attributes.

Trac ticket: https://core.trac.wordpress.org/ticket/60282

#37 @gziolo
3 years ago

  • Resolution → fixed
  • Status reopened → closed

In 57574:

Editor: Refactor block binding processing and attribute computation

Refactors the processing of block bindings into steps:

  • Gets the value for each "bound" attribute from the respective source.
  • Returns the computer attributes with values from the sources.
  • The computed attributes get injected into block's content.
  • The render_callback gets the updated list of attributes and processeded block content.

Fixes #60282.
Props czapla, gziolo, andraganescu, santosguillamot.

#39 @gziolo
3 years ago

In 57575:

Editor: Add wakeup magic method to the block bindings registry

See #60282.
Follow-up [57373].
Props dmsnell, mukesh27, gziolo.

​@gziolo commented on ​PR #6059:


3 years ago
#40

Currently the binding process is completely on, nether $block_content = $this->process_block_bindings( $block_content ); or $modified_block_content = $this->replace_html are filtered. Is it too soon to consider filters? Maybe it is.

@draganescu, we could always discuss introducing new settings for the block binding source:

  • filter_callback to post-process the raw value returned by the source, example: date formatting.
  • should_replace_callback to run before $this->replace_html() that would let folks decide whether to replace the value for an attribute in HTML.

We should be iterating on the public API as we learn about the most common needs reported by developers.

#41 @stevenlinx
3 years ago

  • Keywords needs-dev-note added

as part of the standalone dev note post.

This ticket was mentioned in ​PR #13930 on ​WordPress/wordpress-develop by ​@huzaifaalmesbah.


7 days ago
#42

Description

Context

In ​#13902 (landed in [https://core.trac.wordpress.org/changeset/64031 [64031]]), we documented intentional loose comparisons in the Block Bindings test suite. During review, core committer @lancewillett noted an existing test coverage gap:

*"Optional follow-up, new PR: assert that unregistering also removes the source from the registry. This existing gap needn’t block these comment-only changes."*

What this PR does

Previously, test_unregister_block_source() only asserted that unregister_block_bindings_source() and WP_Block_Bindings_Registry::unregister() returned the unregistered source object. However, neither test verified that the source was actually removed from internal state.

This PR closes that gap by asserting that the source is no longer present in the registry after unregistration:

  1. tests/phpunit/tests/block-bindings/register.php:
    • Asserts that get_block_bindings_source( self::$test_source_name ) returns null after unregistration.
    • Updates the @covers annotation to include ::get_block_bindings_source.
  1. tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php:
    • Asserts that $this->registry->get_registered( self::$test_source_name ) returns null.
    • Asserts that $this->registry->is_registered( self::$test_source_name ) returns false.
    • Updates the @covers annotations to include WP_Block_Bindings_Registry::get_registered and WP_Block_Bindings_Registry::is_registered.

Scope

  • 100% test-only change; zero production code modified.
  • Adds 3 new assertions, increasing total assertions from 108 to 111 with 100% passing tests.

---

Testing Instructions

Run the block-bindings test suite locally:

# Single site suite
npm run test:php -- --test-suffix .php tests/phpunit/tests/block-bindings

# Multisite suite
npm run test:php -- -c tests/phpunit/multisite.xml --test-suffix .php tests/phpunit/tests/block-bindings

# Code style check
composer run lint tests/phpunit/tests/block-bindings

Expected result: 54 tests, 111 assertions (OK, 100% pass), 0 lint errors.

---

Follow-up to: ​https://github.com/WordPress/wordpress-develop/pull/13902 / https://core.trac.wordpress.org/changeset/64031

Use of AI Tools

AI assistance: Yes
Tool(s): Antigravity
Model(s): Gemini
Used for: Analyzing test gaps, drafting follow-up assertions, and authoring the PR description. Implementation, test execution, and code review were verified locally.

#43 @lancewillett
7 days ago

In 64063:

Tests: Verify block binding sources are removed on unregister.

Assert that unregistered sources can no longer be retrieved through the public helper or registry, and are no longer reported as registered. Returning the source object alone does not prove it was removed.

Follow-up to [64031].

Developed in: ​https://github.com/WordPress/wordpress-develop/pull/13930

Props huzaifaalmesbah.
See #60282.

Note: See TracTickets for help on using tickets.