[wp-trac] [WordPress Trac] #59313: Implement Block Hooks

WordPress Trac noreply at wordpress.org
Thu Sep 7 19:17:15 UTC 2023


#59313: Implement Block Hooks
-----------------------------+---------------------
 Reporter:  Bernhard Reiter  |       Owner:  (none)
     Type:  enhancement      |      Status:  new
 Priority:  high             |   Milestone:  6.4
Component:  Editor           |     Version:
 Severity:  normal           |  Resolution:
 Keywords:                   |     Focuses:
-----------------------------+---------------------
Description changed by Bernhard Reiter:

Old description:

> This ticket is about porting Gutenberg PHP code that implemented Block
> Hooks to Core. Skip to the bottom of this ticket for a TODO list.
>
> == Synopsis
>
> First implemented in Gutenberg under the name
> [https://make.wordpress.org/core/2023/08/10/whats-new-in-
> gutenberg-16-4-9-august/#auto-inserting-blocks "Auto-inserting Blocks"]
> and recently [https://github.com/WordPress/gutenberg/pull/54147 renamed]
> to "Block Hooks", this feature is meant to provide an extensibility
> mechanism for Block Themes, in analogy to WordPress'
> [https://developer.wordpress.org/plugins/hooks/ Hooks] concept that has
> allowed extending Classic Themes through filters and actions.
>
> Specifically, Block Hooks allow a third-party block to specify a position
> relative to a given block into which it will then be automatically
> inserted (e.g. a "Like" button block can ask to be inserted after the
> Post Content block, or an eCommerce shopping cart block can ask to be
> inserted after the Navigation block).
>
> The two core tenets for block hooks are:
>
> 1. Insertion into the frontend should happen right after a plugin
> containing a hooked block is activated (i.e. the user isn't required to
> insert the block manually in the editor first); similarly, disabling the
> plugin should remove the hooked block from the frontend.
> 2. The user has the ultimate power to customize that automatic insertion:
> The hooked block is also visible in the editor, and the user's decision
> to persist, dismiss, customize, or move it will be respected (and
> reflected on the frontend).
>
> To account for both tenets, we've made the **tradeoff** that automatic
> block insertion only works for unmodified templates (and template parts,
> respectively). The reason for this is that the simplest way of storing
> the information whether a block has been persisted to (or dismissed from)
> a given template (or part) is right in the template markup. This was
> first suggested
> [https://github.com/WordPress/gutenberg/issues/39439#issuecomment-1150278043
> here].
>
> To accommodate for that tradeoff, we've
> [https://github.com/WordPress/gutenberg/pull/52969 added UI controls
> (toggles)] to increase visibility of hooked blocks, and to allow their
> later insertion into templates (or parts) that already have been modified
> by the user.
>
> == Implementation
>
> Since we wanted hooked blocks to appear both in the frontend and in the
> editor (see tenet number 2), we had to make sure they were inserted into
> both the frontend markup and the REST API (templates and patterns
> endpoints) equally. As a consequence, this means that automatic insertion
> couldn't only be implemented at block ''render'' stage, as for the
> editor, we needed to modify the ''serialized'' (but ''unrendered'')
> markup.
>
> However, thanks to the tradeoff we made (see above), we could at least
> limit ourselves to only inserting hooked blocks into unmodified templates
> (and parts), i.e. those that were coming directly from a Block Theme's
> template (or parts) file, rather than the database.
>
> It turns out that there's a rather natural stage for automatic insertion
> of hooked blocks to happen, which is during template file retrieval.
>
> While we had to leverage the `get_block_templates` and
> `get_block_file_template` hooks in Gutenberg to insert those blocks,
> we'll be able to directly modify the
> `_build_block_template_result_from_file` function in Core.
>
> Furthermore, hooked blocks also have to be inserted into block patterns.
> Since almost no filters exist for the patterns registry, this was done in
> the patterns REST API controller in the Gutenberg codebase; for Core,
> we'll likely want to this at a lower level.
>
> == Core merge
>
> Based on an early exploration in this [https://github.com/WordPress
> /wordpress-develop/pull/5158 experimental PR], I believe that we can
> break down the implementation of Block Hooks in Core into the following
> steps. Note that due to the nature of the existing code, the first steps
> might appear somewhat surprising and unrelated; however, they are very
> much relevant and will allow us to extend existing functionality through
> a series of self-contained steps.
>
> - Add proper unit test coverage to verify that
> `_build_block_template_result_from_file` injects the `theme` attribute.
>   - While we have coverage for
> `_inject_theme_attribute_in_block_template_content`, those tests only
> verify that ''that'' function does what is supposed to do; there's
> however no guarantee that `_build_block_template_result_from_file` uses
> that function (or whatever other technique) to actually inject the theme
> attribute ([https://github.com/WordPress/wordpress-develop/pull/5155
> see]).
> - Add a `$callback` argument to `serialize_block()`
> ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/b926b86f1585bebcc71efae4819cb8b4593e8714 see])
> and `serialize_blocks()` ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/7fda6f56ef2c49ccdc687e15c3c5bb914290d94c see]).
>   - In order to insert our hooked blocks, we need to traverse the parsed
> block tree (before it is eventually serialized). In order to minimize the
> number of tree traversals (which is a potentially costly operation), it
> seems that adding a callback function argument to `serialize_block()` --
> which inevitable has to traverse the entire tree anyway -- is a natural
> fit.
> - Use the latter to replace the call to
> `_inject_theme_attribute_in_block_template_content` in
> `_build_block_template_result_from_file` with a newly written
> `_inject_theme_attribute_in_template_part_block`
> ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/f7465b97001313ec525215b1e768f9142f252d56 see]).
>   - Note that `_inject_theme_attribute_in_block_template_content` is
> still used in another spot: The Template Part block’s render.php (i.e. in
> GB). We might also want to replace that with
> `_inject_theme_attribute_in_template_part_block` so that we can deprecate
> `_inject_theme_attribute_in_block_template_content`.
> - Add `block_hooks` field to `WP_Block_Type`, `block.json` loading, REST
> API endpoint, etc ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/3ad1fd7caac337e10d385fee2619b53ceb4f7c0a see]).
> - Implement `get_hooked_blocks()` to receive a list of blocks hooked into
> a given "anchor" block ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/cdcfbcc1117566df0b608f1875d50480b92111ad see]).
> - Use that to implement `insert_hooked_blocks`, and call the latter
> alongside `_inject_theme_attribute_in_template_part_block` in a newly
> written visitor function that's passed as `$callback` to
> `serialize_blocks` ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/c897d86393c2b434421894a776c6e2b75209a186 see]).
> - Apply automatic insertion of hooked blocks to block patterns
> ([https://github.com/WordPress/gutenberg/blob/7b68e34bb04d18619dbe4140e870a4d6bb28873d/lib/experimental
> /class-gutenberg-rest-block-patterns-controller.php#L20-L21 see]).
> - Add a filter to allow people to e.g. limit automatic insertion of
> hooked blocks to a certain template or template part type only
> ([https://github.com/WordPress/wordpress-
> develop/pull/5158/commits/c897d86393c2b434421894a776c6e2b75209a186 also
> see]).
>   - This somewhat informs the architecture of the code (e.g. function
> signatures, passing of `$block_template` context) and goes beyond what is
> currently in Gutenberg.
>   - Note that this hasn't been implemented in the code sync PR yet and is
> thus still a bit more tentative.

New description:

 This ticket is about porting Gutenberg PHP code that implemented Block
 Hooks to Core. Skip to the bottom of this ticket for a TODO list.

 == Synopsis

 First implemented in Gutenberg under the name
 [https://make.wordpress.org/core/2023/08/10/whats-new-in-
 gutenberg-16-4-9-august/#auto-inserting-blocks "Auto-inserting Blocks"]
 and recently [https://github.com/WordPress/gutenberg/pull/54147 renamed]
 to "Block Hooks", this feature is meant to provide an extensibility
 mechanism for Block Themes, in analogy to WordPress'
 [https://developer.wordpress.org/plugins/hooks/ Hooks] concept that has
 allowed extending Classic Themes through filters and actions.

 Specifically, Block Hooks allow a third-party block to specify a position
 relative to a given block into which it will then be automatically
 inserted (e.g. a "Like" button block can ask to be inserted after the Post
 Content block, or an eCommerce shopping cart block can ask to be inserted
 after the Navigation block).

 The two core tenets for block hooks are:

 1. Insertion into the frontend should happen right after a plugin
 containing a hooked block is activated (i.e. the user isn't required to
 insert the block manually in the editor first); similarly, disabling the
 plugin should remove the hooked block from the frontend.
 2. The user has the ultimate power to customize that automatic insertion:
 The hooked block is also visible in the editor, and the user's decision to
 persist, dismiss, customize, or move it will be respected (and reflected
 on the frontend).

 To account for both tenets, we've made the **tradeoff** that automatic
 block insertion only works for unmodified templates (and template parts,
 respectively). The reason for this is that the simplest way of storing the
 information whether a block has been persisted to (or dismissed from) a
 given template (or part) is right in the template markup. This was first
 suggested
 [https://github.com/WordPress/gutenberg/issues/39439#issuecomment-1150278043
 here].

 To accommodate for that tradeoff, we've
 [https://github.com/WordPress/gutenberg/pull/52969 added UI controls
 (toggles)] to increase visibility of hooked blocks, and to allow their
 later insertion into templates (or parts) that already have been modified
 by the user.

 == Implementation

 Since we wanted hooked blocks to appear both in the frontend and in the
 editor (see tenet number 2), we had to make sure they were inserted into
 both the frontend markup and the REST API (templates and patterns
 endpoints) equally. As a consequence, this means that automatic insertion
 couldn't only be implemented at block ''render'' stage, as for the editor,
 we needed to modify the ''serialized'' (but ''unrendered'') markup.

 However, thanks to the tradeoff we made (see above), we could at least
 limit ourselves to only inserting hooked blocks into unmodified templates
 (and parts), i.e. those that were coming directly from a Block Theme's
 template (or parts) file, rather than the database.

 It turns out that there's a rather natural stage for automatic insertion
 of hooked blocks to happen, which is during template file retrieval.

 While we had to leverage the `get_block_templates` and
 `get_block_file_template` hooks in Gutenberg to insert those blocks, we'll
 be able to directly modify the `_build_block_template_result_from_file`
 function in Core.

 Furthermore, hooked blocks also have to be inserted into block patterns.
 Since almost no filters exist for the patterns registry, this was done in
 the patterns REST API controller in the Gutenberg codebase; for Core,
 we'll likely want to this at a lower level.

 == Core merge

 Based on an early exploration in this [https://github.com/WordPress
 /wordpress-develop/pull/5158 experimental PR], I believe that we can break
 down the implementation of Block Hooks in Core into the following steps.
 Note that due to the nature of the existing code, the first steps might
 appear somewhat surprising and unrelated; however, they are very much
 relevant and will allow us to extend existing functionality through a
 series of self-contained steps.

 - Add proper unit test coverage to verify that
 `_build_block_template_result_from_file` injects the `theme` attribute.
   - While we have coverage for
 `_inject_theme_attribute_in_block_template_content`, those tests only
 verify that ''that'' function does what is supposed to do; there's however
 no guarantee that `_build_block_template_result_from_file` uses that
 function (or whatever other technique) to actually inject the theme
 attribute ([https://github.com/WordPress/wordpress-develop/pull/5155
 see]).
 - Add a `$callback` argument to `serialize_block()`
 ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/b926b86f1585bebcc71efae4819cb8b4593e8714 see])
 and `serialize_blocks()` ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/7fda6f56ef2c49ccdc687e15c3c5bb914290d94c see]).
   - In order to insert our hooked blocks, we need to traverse the parsed
 block tree (before it is eventually serialized). In order to minimize the
 number of tree traversals (which is a potentially costly operation), it
 seems that adding a callback function argument to `serialize_block()` --
 which inevitable has to traverse the entire tree anyway -- is a natural
 fit.
 - Use the latter to replace the call to
 `_inject_theme_attribute_in_block_template_content` in
 `_build_block_template_result_from_file` with a newly written
 `_inject_theme_attribute_in_template_part_block`
 ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/f7465b97001313ec525215b1e768f9142f252d56 see]).
   - Note that `_inject_theme_attribute_in_block_template_content` is still
 used in another spot: The Template Part block’s render.php (i.e. in GB).
 We might also want to replace that with
 `_inject_theme_attribute_in_template_part_block` so that we can deprecate
 `_inject_theme_attribute_in_block_template_content`.
 - Add `block_hooks` field to `WP_Block_Type`, `block.json` loading, REST
 API endpoint, etc ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/3ad1fd7caac337e10d385fee2619b53ceb4f7c0a see]).
 - Implement `get_hooked_blocks()` to receive a list of blocks hooked into
 a given "anchor" block ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/cdcfbcc1117566df0b608f1875d50480b92111ad see]).
 - Use that to implement `insert_hooked_blocks`, and call the latter
 alongside `_inject_theme_attribute_in_template_part_block` in a newly
 written visitor function that's passed as `$callback` to
 `serialize_blocks` ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/c897d86393c2b434421894a776c6e2b75209a186 see]).
 - Apply automatic insertion of hooked blocks to block patterns
 ([https://github.com/WordPress/gutenberg/blob/7b68e34bb04d18619dbe4140e870a4d6bb28873d/lib/experimental
 /class-gutenberg-rest-block-patterns-controller.php#L20-L21 see]).
 - Add a filter to allow people to e.g. limit automatic insertion of hooked
 blocks to a certain template or template part type only
 ([https://github.com/WordPress/wordpress-
 develop/pull/5158/commits/c897d86393c2b434421894a776c6e2b75209a186 also
 see]).
   - This somewhat informs the architecture of the code (e.g. function
 signatures, passing of `$block_template` context) and goes beyond what is
 currently in Gutenberg.
   - Note that this hasn't been implemented in the code sync PR yet and is
 thus still a bit more tentative.

 ''The TODO list is based on my notes from a call with @gziolo earlier
 today.''

--

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/59313#comment:1>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list