Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Hooks: Respect "multiple": false #7443

Conversation

ockham
Copy link
Contributor

@ockham ockham commented Sep 26, 2024

Block Hooks currently do not respect the "multiple": false setting: If this is set in a prospective hooked block's block.json, and there's already an instance of that block present in a given context (i.e. template, template part, pattern, or navigation menu), then Block Hooks should not insert another instance of that block.

In a similar vein, if no other instance of that block is present in the given context, then Block Hooks should insert only one instance of that block -- i.e. the first time a matching anchor block is encountered, but not for any other matching anchor blocks.

This will help extenders avoid checking for the presence of another instance of their hooked block inside of the hooked_block_types filter, which is called for every block found in a given context -- thus introducing an unnecessary performance overhead.

Implementation notes

This is implemented in apply_block_hooks_to_content -- which as of 59101 is the main entrypoint for applying Block Hooks to block markup -- as follows:

First, we iterate over all "statically" registered hooked blocks (i.e. via a block.json's blockHooks field) and check for each of them if it has multiple set to false. If it does, and if there's already an instance of the block present in the markup, then we remove the block from the list of hooked blocks passed to the Block Hooks logic. Otherwise (i.e. if multiple is true, or if the block is not present in the markup), we cache the value of its multiple field in the $block_allows_multiple_instances array.

We then create an anonymous function for use with the hooked_block_types filter, with PHP_INT_MAX as its priority. This allows us to keep track if we've already inserted one instance of a hooked block that has "multiple": false, and to also "intercept" hooked blocks that are "dynamically" added, i.e. via the hooked_block_types filter.

Architectural notes

The chosen architecture has several benefits. One, unlike most other Block Hooks related functions, apply_block_hooks_to_content gets $content as an argument (i.e. a block markup string), which means we don't have to awkwardly extract it from the $context variable (that's conteXt, not conteNt), as seen e.g. here in this alternative PR.

Two, using the hooked_block_types filter (at maximum priority) allows us to communicate both the aforementioned $content string further "down" (so we can check for the presence of the hooked block in the markup), as well as the $block_allows_multiple_instances cache. Otherwise, we'd likely have to add those as arguments to all functions along the call stack from apply_block_hooks_to_content all the way down to insert_hooked_blocks (thus also diluting separation of concerns). The present approach OTOH keeps things neatly separated.

Testing instructions

You can use the Like Button block plugin to test; the latest version (0.9.0) has "multiple": false set in its block.json.

However, note that you need to modify the plugin prior to activating it:

  1. You need to disable the hooked_block_types filter that would normally auto-insert the block. (You can also do this manually via Tools > Plugin File Editor after installing the plugin -- just make sure it's before activating it!)
diff --git a/like-button.php b/like-button.php
index 67c41ca..09ef0a7 100644
--- a/like-button.php
+++ b/like-button.php
@@ -26,7 +26,7 @@ function add_like_button_block_after_post_content_block( $hooked_block_types, $r
        }
        return $hooked_block_types;
 }
-add_filter( 'hooked_block_types', 'add_like_button_block_after_post_content_block', 10, 4 );
+//add_filter( 'hooked_block_types', 'add_like_button_block_after_post_content_block', 10, 4 );
 
 function set_like_button_block_layout_attribute_based_on_adjacent_block( $hooked_block, $hooked_block_type, $relative_position, $anchor_block ) {
        // Is the hooked block adjacent to the anchor block?
  1. Now activate the plugin, and use the Site Editor to insert the Like Button block into the Single template. Choose a position where the plugin would normally auto-insert it, i.e. not directly below the Post Content block. However, it needs to be inside the actual Single template, and not inside an area that's actually handled by a template part or Navigation menu. Then, save the template and verify on the frontend that the block is present where it should be.
  2. Now re-enable auto-insertion, i.e. revert step 1.
  3. Reload the frontend to verify that the block is indeed not auto-inserted, as it respects the `"multiple": false" setting.
  4. Bonus points if you remove the "multiple": false line from block.json to verify that it will auto-insert the block if that line is not present.

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

Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@ockham ockham force-pushed the update/have-block-hooks-respect-multiple-false-in-apply-block-hooks-to-content branch from 4af6a2b to dcbdba9 Compare September 27, 2024 09:28
@ockham ockham self-assigned this Sep 27, 2024
@ockham ockham marked this pull request as ready for review September 27, 2024 10:30
Copy link

github-actions bot commented Sep 27, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props bernhard-reiter, jonsurrell, gziolo.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Member

@sirreal sirreal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reviewing this, a few preliminary stylistic notes.

src/wp-includes/blocks.php Outdated Show resolved Hide resolved
src/wp-includes/blocks.php Outdated Show resolved Hide resolved
@ockham
Copy link
Contributor Author

ockham commented Sep 27, 2024

Note to self, this is going to need a Dev Note.

src/wp-includes/blocks.php Outdated Show resolved Hide resolved
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice unit tests coverage. I see all the cases I could think of covered which gives a lot of confidence into the implementation proposed.

* `$content` at first and we're allowed to insert it once -- but not again.
*/
$suppress_single_instance_blocks = static function ( $hooked_block_types ) use ( &$block_allows_multiple_instances, $content ) {
static $single_instance_blocks_present_in_content = array();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the only place where I'm not entirely sure what are the consequences of using the static variable. However my understanding is that $suppress_single_instance_blocks should be instantiated once per apply_block_hooks_to_content call, so all the checks are applied in a specific context - a template, template part, pattern, post content or navigation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my testing, it works correctly as explained in #7443 (comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, IIUC, we're instantiating a new function here every time apply_block_hooks_to_content() is called, and for the lifetime of that $suppress_single_instance_blocks, we're using $single_instance_blocks_present_in_content as a cache. The next call to apply_block_hooks_to_content() then creates a new $suppress_single_instance_blocks, which in turn creates a new (empty) $single_instance_blocks_present_in_content.

(I added a var_dump( $single_instance_blocks_present_in_content ); on the line below this one and ran tests to verify this 😅 )

@gziolo
Copy link
Member

gziolo commented Sep 30, 2024

Tested with the Like Button and the following changes:

Screenshot 2024-09-30 at 13 37 11

I can confirm that the block gets only inserted once in the related area of the template or pattern:

Screenshot 2024-09-30 at 13 37 18 Screenshot 2024-09-30 at 13 37 24

Well, it all works as expected on the templating level. In the final page rendered on the site, it's difficult to change some of the behaviors like the Comments block using the template from Comment template that works as a repeater field, so that's why we still see multiple instances of the Like block.

@ockham
Copy link
Contributor Author

ockham commented Sep 30, 2024

Thank you very much for reviewing and approving!

Well, it all works as expected on the templating level. In the final page rendered on the site, it's difficult to change some of the behaviors like the Comments block using the template from Comment template that works as a repeater field, so that's why we still see multiple instances of the Like block.

Right, that's a good point that hadn't occurred to me. The same template part is used multiple times — but since we only enforce single-instance insertion on a per-context (here: per-template-part) basis, there is still one Like Button per template part — and thus, multiple Like buttons in the Comments section, as you said 😅

I'm not sure there's an easy way to change this so there would be only one instance in the entire rendered output, as that would have to work across context boundaries. We might look into that later (for 6.8?) -- assuming we can even easily define the desired behavior in that case -- but it's clearly out of scope for 6.7.

I'll go and commit this change 😄

@gziolo
Copy link
Member

gziolo commented Sep 30, 2024

I'm not sure there's an easy way to change this so there would be only one instance in the entire rendered output, as that would have to work across context boundaries. We might look into that later (for 6.8?) -- assuming we can even easily define the desired behavior in that case -- but it's clearly out of scope for 6.7.

To be fair, it's exactly the same problem that exists in the editor. It's even more difficult to resolve there as there are multiple factors involved, like you can use a zoom out mode to edit only a pattern, or template part, etc. However, the more challenging factor is that some aspects of the theme might change through external updates, and the same applies to the content. At the end of the day, it all has to be assembled into a single page on the site when rendering for the visitor. It's rather impossible to find a way to represent that in the UI that such block might or might not display in the future.

@ockham
Copy link
Contributor Author

ockham commented Sep 30, 2024

Committed to Core in https://core.trac.wordpress.org/changeset/59124.

@ockham ockham closed this Sep 30, 2024
@ockham ockham deleted the update/have-block-hooks-respect-multiple-false-in-apply-block-hooks-to-content branch October 2, 2024 10:44
@gziolo gziolo mentioned this pull request Oct 10, 2024
69 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants