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

Update Navigation block to render hooked inner blocks #57754

Conversation

tjcafferkey
Copy link
Contributor

@tjcafferkey tjcafferkey commented Jan 11, 2024

Fixes https://core.trac.wordpress.org/ticket/59743.

What?

In this PR we are hooking into two filters. One which allows us to update the API response based on post type (wp_navigation) to insert hooked blocks before it gets returned to the editor. The second allows us to insert additional hooked blocks to the core/navigation via the WP_Navigation_Block_Renderer class before rendering happens on the frontend.

Notes

This solution is very specific to the Navigation block and is not scalable when it comes to implementing it for other blocks. From my early understanding this is necessary since the Navigation block works slightly differently:

  • The contents of the Navigation block is saved to the database as its own entity in wp_posts with a post_type of wp_navigation.
  • The Navigation block hijacks the server-side rendering process in a very explicit way using WP_Navigation_Block_Renderer to render it on the server. I believe this is due to a number of reasons but one of them being that it contains logic to wrap certain inner blocks in <nav> and <li> tags.
  • This approach passes the current block as the $context value to the hook in addition to templates and patterns. You can differentiate this by checking that $context['blockName'] is set to determine if it's a block.

Todo

  • Insert hooked first_child and last_child blocks as inner blocks into the core Navigation block.
  • Investigate how this impacts fallback navigations / saved navigations.
  • Ensure that if a hooked block is removed from the core Navigation block that it remains removed.
  • Refactor code to reuse some utility functions used to insert hooked blocks into templates/parts/patterns.
  • Bypass unnecessary serialization/deserialization that is currently happening in the code.
  • Unit tests.

Related: woocommerce/woocommerce#43498

Why?

How?

Testing Instructions

  1. Add the below code to your themes functions.php
  2. Load frontend and check the Login/Logout block is an inner block of the core Navigation block and hasn't been added twice.
  3. Load the Header template part in the Site Editor and check that the Login/Logout block is present as an inner block of the core Navigation and hasn't been added twice.
  4. Make customisations to move the block and check it persists between reloads.
  5. Make customisations to remove the block completely and check it persists between reloads.
  6. Remove the PHP code added in step 1, and now add the below JSON to the same blocks block.json file and retest starting from step 2.
function register_logout_block_as_navigation_last_child( $hooked_blocks, $position, $anchor_block, $context ) {
	if ( $anchor_block === 'core/navigation' && $position === 'last_child' ) {
		$hooked_blocks[] = 'core/loginout';
	}

	return $hooked_blocks;
}

add_filter( 'hooked_block_types', 'register_logout_block_as_navigation_last_child', 10, 4 );
"blockHooks": {
	"core/navigation": "lastChild"
}

@ockham
Copy link
Contributor

ockham commented Jan 11, 2024

Nice, thanks again for working on this! I won't be able to review this more in-depth on Monday, so here's just a few high-level notes 😊

It would have been good to use the functions make_before_block_visitor, make_after_block_visitor and traverse_and_serialize_blocks but from my early understanding they may need updating since they look for a $parent_block to insert hooked blocks as first_child and last_child. However in the case of the navigation block it gets given the following post_content from the database: [...]

Right. This is going to be interesting. While we could in theory just run traverse_and_serialize_blocks() on the blocks parsed from that post_content -- which is what we do for templates etc, where the top level blocks also don't have any parents -- this will lose the information that they are indeed "children" of a Navigation block. We might be able to provide a "fake" parent Navigation block and pass that to traverse_and_serialize_block() (note the singular!), but this is still likely to have some issues.

Chief among them is that per WordPress/wordpress-develop#5712, we use anchor blocks to store the information if a hooked block has been touched by the user.

This is important in order to ensure that we stop inserting the hooked block if the user has persisted or dismissed it; in case of the Navigation block, this means e.g.

  • Mini Cart block is registered as a hooked block.
  • User edits Navigation menu, decides to delete Mini Cart block from there, saves menu.
  • The next time it's loaded on the frontend, Block Hooks mechanism still renders it (as it's ignorant of the user's intent) 😖

So that's going to be one of the core problems to solve here 😅

@tjcafferkey
Copy link
Contributor Author

@ockham just an update:

We might be able to provide a "fake" parent Navigation block and pass that to traverse_and_serialize_block() (note the singular!), but this is still likely to have some issues.

This seems to work relatively well currently but when it comes to noting that a hooked block has been removed we may need to revisit this since we don't get any data about the parent block which will contain removed hooked blocks information.

Additional to the above, I've made a note in the comment of the current code. At the moment there's some unnecessary serializing and deserializing happening because we're using the traverse_and_serialize_block and we don't necessarily want serialized data at this point since we need to remove the mocked parent navigation block and only return the innerBlocks of it.

Perhaps there's scope to separate the two responsibilities of this function to traverse_block and serialize_traversed_block which traverse_and_serialize_block could be refactored to use. This would allow us to use traverse_block separately. Again, haven't had a chance to look at how easy that would be yet but just a thought.

@ockham
Copy link
Contributor

ockham commented Jan 15, 2024

We might be able to provide a "fake" parent Navigation block and pass that to traverse_and_serialize_block() (note the singular!), but this is still likely to have some issues.

This seems to work relatively well currently but when it comes to noting that a hooked block has been removed we may need to revisit this since we don't get any data about the parent block which will contain removed hooked blocks information.

Yeah, that's the major challenge here. While we normally (per WordPress/wordpress-develop#5712) use the metadata.ignoredHookedBlocks attribute on the anchor block, this becomes harder here, as the anchor block -- i.e. the parent, in case of child insertion -- is sort of detached from its children.

So we need to find a way to carry over that meta information about ignored hooked blocks, or store it differently.

I think I had an idea or two how we might pull this off. Since we're dealing with a WP_Post object (for the wp_navigation CPT), how about we add a post meta field to hold that information?

The way to do this would be to basically take the mocked navigation block after we've run traverse_and_serialize_block() on it. At that point, it will have the information we're looking for in its metadata.ignoredHookedBlocks attribute, which we can then sync over to the WP_Post object's meta.

(Since WordPress/wordpress-develop#5712 was only merged rather recently, your dev environment needs to run WordPress trunk rather than 6.4)

Additional to the above, I've made a note in the comment of the current code. At the moment there's some unnecessary serializing and deserializing happening because we're using the traverse_and_serialize_block and we don't necessarily want serialized data at this point since we need to remove the mocked parent navigation block and only return the innerBlocks of it.

Perhaps there's scope to separate the two responsibilities of this function to traverse_block and serialize_traversed_block which traverse_and_serialize_block could be refactored to use. This would allow us to use traverse_block separately. Again, haven't had a chance to look at how easy that would be yet but just a thought.

Good observation! There might be a number of different ways to tackle this (related: WordPress/wordpress-develop#5753), but TBH, I'd rather look into this only once we got hooked inner blocks insertion working 🙂

@tjcafferkey
Copy link
Contributor Author

tjcafferkey commented Jan 16, 2024

@ockham just an update on some changes to this PR where I've experimented with getting the ignoredHookedBlocks data.

  • aeaa43a - This commit exposes the navigation block being rendered on the server via the filter to enable us to get the ignoredHookedBlocks attribute data.
  • 4b3a1a5 - This commit passes the ignoredHookedBlocks along with the request on the editor side which we can access for this specific data. Another solution to this problem might be to just return the hooked blocks in the response and then remove them on the client-side before render and although I considered it I don't think its the right approach and we should consistently use the functions provided to not insert blocks on the server where possible.

I've not done extensive testing to check this works in all cases but from some preliminary tests it works if you add the following attribute to your navigation block:

"metadata":{"ignoredHookedBlocks":["core/loginout"]}

One thing I've not looked into yet is adding this attribute to the block when the user action of removing a hooked block is performed.

As always, open to feedback / improvements / better ideas of approaches.

@ockham
Copy link
Contributor

ockham commented Jan 16, 2024

Oh, this is coming together nicely! 😄

diff --git a/packages/block-library/src/loginout/block.json b/packages/block-library/src/loginout/block.json
index 59fceec596e..62e57c6b430 100644
--- a/packages/block-library/src/loginout/block.json
+++ b/packages/block-library/src/loginout/block.json
@@ -40,5 +40,8 @@
                                "fontSize": true
                        }
                }
+       },
+       "blockHooks": {
+               "core/navigation": "lastChild"
        }
 }

gives me the following:

hooked-block-in-navigation

It's lovely to see how the block is present in the editor, and how it can be moved around within the Navigation block, just like we'd want it to!

@ockham
Copy link
Contributor

ockham commented Jan 16, 2024

One thing I've not looked into yet is adding this attribute to the block when the user action of removing a hooked block is performed.

So the underlying paradigm for Block Hooks is to handle pretty much everything on the server side, and rely on the editor only implicitly. (This is originally motivated by the requirement that a hooked block needs to be rendered on the frontend even if the Site Editor has never been visited -- which means that the mechanism needs to work solely on the server side.)

As a corollary, the ignoreHookedBlocks metadata is never explicitly set in the editor. Instead, it is injected into an anchor block when the Block Hooks algorithm runs (on the server side, for a REST API request) and inserts the relevant hooked blocks for the given anchor block. This means that if a template is loaded in the Site Editor, any anchor blocks within will have their metadata.ignoreHookedBlocks attributes set to contain their respective hooked blocks.

Now if the template is saved, these attributes will be saved to the database as well! (Inside the corresponding wp_template CPT.) The next time the Block Hooks algorithm runs (whether on the frontend or again for the Site Editor, i.e. in an REST API controller), it will encounter those attributes, and as a consequence, will not attempt to inject the hooked blocks that are already on record!


We'll need some way to emulate this for the Navigation block. Currently, the metadata.ignoreHookedBlocks attribute is missing from it (when it does indeed have a hooked block) when loading it in the editor (see near the end of my GIF). Maybe we can do something on the server side to set that attribute when we know that a hooked block has been inserted?

It might be tricky though, since the block and its corresponding wp_navigation post are quite detached from each other. This is also evidenced in the Code Editor: The Navigation block is quite opaque -- its inner block tree isn't represented there.

<!-- wp:navigation {"ref":4,"layout":{"type":"flex","justifyContent":"right","orientation":"horizontal"},"style":{"spacing":{"margin":{"top":"0"},"blockGap":"var:preset|spacing|20"},"layout":{"selfStretch":"fit","flexSize":null}}} /-->

This makes it a lot harder to set the metadata.ignoredHookedBlocks attribute when a Navigation block is encountered e.g. within a template part (as it usually is). During traversal of that template part, the Block Hooks mechanism doesn't have any way of knowing which blocks have been inserted into the corresponding wp_navigation CPT without descending into madness that CPT -- i.e. it'd need to load that wp_navigation object. This would be bad for a number of reasons, mostly due to added complexity and mixed concerns.

This is what makes me think that we might want to persist the ignoredHookedBlocks information differently. I think it'd might make sense to try and colocate ignoredHookedBlocks alongside the actual wp_navigation CPT. This is what I meant with this part:

Since we're dealing with a WP_Post object (for the wp_navigation CPT), how about we add a post meta field to hold that information?

The way to do this would be to basically take the mocked navigation block after we've run traverse_and_serialize_block() on it. At that point, it will have the information we're looking for in its metadata.ignoredHookedBlocks attribute, which we can then sync over to the WP_Post object's meta.

(Since WordPress/wordpress-develop#5712 was only merged rather recently, your dev environment needs to run WordPress trunk rather than 6.4)

We'd probably need to make some minor adjustments on the editor side to keep that meta information around, and use it when updating the wp_navigation CPT via the REST API.

LMK if this makes sense to you, and if you're up for giving it a try! I realize it's maybe not the most obvious thing, so we can also get on a call some time this week to work on this together 😄

@tjcafferkey
Copy link
Contributor Author

Thanks Bernie, just to summarise our Slack conversation and next steps here:

Summary

  • The navigation block is a stored database entry. Meaning we can store additional meta data in a post_meta_field for each different navigation.
  • My original concern was that the user would not be able to use the same navigation in multiple places, and at the same time remove the hooked block selectively in each place.
  • We agreed this is intentional behaviour since the entire navigation blocks block tree is stored in the database. Meaning if updated in one navigation it should apply to all them. This remains true irrespective of whether the change is made via the Block Hooks API or not.

Next steps:

  • I'm going to look at reverting my current approach and implement the approach you suggested.

@tjcafferkey
Copy link
Contributor Author

tjcafferkey commented Jan 18, 2024

I think I had an idea or two how we might pull this off. Since we're dealing with a WP_Post object (for the wp_navigation CPT), how about we add a post meta field to hold that information?

Thoughts on this approach having worked on implementing it.

Typical process of inserting a hooked block

Template parts from the filesystem:
Filter runs > Parses markup and inserts hooked block > Adds to ignoredHookedBlocks attribute > Render > Reload page > Back to start.

Template parts from the database:
Filter runs > Parses markup, sees ignoredHookedBlocks attribute > Does not insert booked block if present > Render > Reload page > Back to start.

Proposed process of inserting a hooked inner block into the Navigation block

Proposed approach:
Filter runs > Checks for presence of ignoredHookedBlocks in post_meta_field (not there on first render) > Parses markup and inserts hooked block if not present > Adds hooked block to ignoredHookedBlocks in post_meta_field > Render > Reload page > Back to start

The problem:
On second page load during this phase [[ Checks for presence of ignoredHookedBlocks in post_meta_field ]] our hooked block will be in here, preventing it from being inserted.

Somehow, we only want to store our hooked block in the post_meta_field when the user is updating the navigation and saving it to the database. At which point, the hooked block will be saved within the wp_navigation markup, and will also be present in the post_meta_field meaning it won’t be inserted in a second time. It also means that if the user has removed the hooked block and saved the navigation it won't be inserted once gain..

Furthermore, currently a user can reset their ignoredHookedBlocks values by clearing customisations on their templates. They will not have any way to achieve this with the navigation block as far as I understand it with this approach. Do we think this is OK?

@tjcafferkey
Copy link
Contributor Author

The following conditions detailed here loads menu items from an 'unstable location'. I'm not sure how easy it would be to track ignoredHookedBlocks for these menu types meaning we could end up hooking duplicate blocks into them.

One option would be to not hook them in at all, we can do this by checking if there is a navigation ID.

@ockham
Copy link
Contributor

ockham commented Jan 18, 2024

Thanks a lot for clearly spelling out the current process, and the one we've discussed for the Navigation block!

Typical process of inserting a hooked block

Template parts from the filesystem: Filter runs > Parses markup and inserts hooked block > Adds to ignoredHookedBlocks attribute > Render > Reload page > Back to start.

There's one small aspect I feel it should clarify: When loaded on the frontend, the "Adds to ignoredHookedBlocks attribute" step is without consequence, as the attribute isn't persisted back to the database. It's only relevant when loaded in the editor (via the REST API), as it will be included in the markup upon saving. This is somewhat relevant for what we're trying to do with the Navigation block.

(FWIW, we could theoretically even add something like a if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { check around the "Adds to ignoredHookedBlocks attribute" step to only run it when we're processing a REST API query. In fact, I was planning to do so for a while, but it turned out that it makes test coverage a lot harder (if not impossible) as we'd need to deal with that pesky constant that cannot be mocked (or unset once set).)

Template parts from the database: Filter runs > Parses markup, sees ignoredHookedBlocks attribute > Does not insert booked block if present > Render > Reload page > Back to start.

Proposed process of inserting a hooked inner block into the Navigation block

Proposed approach: Filter runs > Checks for presence of ignoredHookedBlocks in post_meta_field (not there on first render) > Parses markup and inserts hooked block if not present > Adds hooked block to ignoredHookedBlocks in post_meta_field > Render > Reload page > Back to start

The problem: On second page load during this phase [[ Checks for presence of ignoredHookedBlocks in post_meta_field ]] our hooked block will be in here, preventing it from being inserted.

Somehow, we only want to store our hooked block in the post_meta_field when the user is updating the navigation and saving it to the database.

Right; which is somewhat equivalent to what I pointed out above about the "typical process": We don't want to persist the ignoredHookedBlocks information upon a read operation, but only on write to DB.

At which point, the hooked block will be saved within the wp_navigation markup, and will also be present in the post_meta_field meaning it won’t be inserted in a second time. It also means that if the user has removed the hooked block and saved the navigation it won't be inserted once gain..

👍

Furthermore, currently a user can reset their ignoredHookedBlocks values by clearing customisations on their templates. They will not have any way to achieve this with the navigation block as far as I understand it with this approach. Do we think this is OK?

Off the top of my head, I'd say that that's fine. Clearing customizations for a template or template part means basically deleting its corresponding wp_template DB entry, which will cause WP to fall back to whatever block file template (part) file is included with the theme.

If we ever feel the need, it'll be possible to introduce a close-enough counterpart for navigation menus, by deleting the corresponding wp_navigation DB entry, which will cause the Navigation block to reset to its fallback (i.e. a page list, I guess). But even in the absence of such an option, I don't think there's any harm to storing ignoredHookedBlocks in the wp_navigation's post meta.

@ockham
Copy link
Contributor

ockham commented Jan 18, 2024

Unfortunately, I'm currently getting an error when saving after moving a hooked block:

navigation-block-error

@ockham
Copy link
Contributor

ockham commented Jan 18, 2024

BTW, when I suggested the following:

Since we're dealing with a WP_Post object (for the wp_navigation CPT), how about we add a post meta field to hold that information?

... I was (erroneously) assuming that we could update the WP_Post without directly persisting the post meta to the database. As discussed on Slack, I realize now that that's not how update_post_meta works 😬


My hope was to emulate the "typical process" by including ignoredHookedBlocks information in the REST API response so it'd be sent back by the editor upon saving, and thus end up persisted in the DB at that point.

I haven't entirely given up on that scenario (mostly in case this error proves to be deeper), but it might be tricky.

I've verified that I can register a meta field to be included in the wp_navigation CPT endpoint (/wp/v2/navigation) response with the following:

diff --git a/lib/compat/wordpress-6.5/navigation-block-hooks.php b/lib/compat/wordpress-6.5/navigation-block-hooks.php
index 48b5533e7a4..98c8276031c 100644
--- a/lib/compat/wordpress-6.5/navigation-block-hooks.php
+++ b/lib/compat/wordpress-6.5/navigation-block-hooks.php
@@ -113,3 +113,18 @@ function gutenberg_hook_first_last_children( $inner_blocks, $post_id = null ) {
        // Note: If there are no hooked inner blocks, we return the original parsed blocks.
        return isset( $parsed_anchor_block_with_hooked_blocks[0]['innerBlocks'] ) ? $parsed_anchor_block_with_hooked_blocks[0]['innerBlocks'] : $inner_blocks;
 }
+
+function register_ignored_hooked_blocks_post_meta() {
+       add_post_type_support( 'wp_navigation', 'custom-fields' );
+       register_post_meta(
+               'wp_navigation',
+               '_wp_ignored_hooked_blocks',
+               array(
+                       'show_in_rest'      => true,
+                       'single'            => true,
+                       'type'              => 'string',
+                       'revisions_enabled' => true,
+               )
+       );
+}
+add_action( 'init', 'register_ignored_hooked_blocks_post_meta' );

(In practice, we'd want the type to be array, but this was just for basic testing purposes.)

To verify, I've manually set a post meta field via WP-CLI:

npm run wp-env run cli wp post meta set 4 _wp_ignored_hooked_blocks core/loginout

This is then indeed included in the entity fetched by useEntityRecords in useNavigationMenu.

However, there are still some pieces missing:

  1. We need to find a way to inject the meta at REST API controller stage (for GET requests), rather than actually creating it in the wp_navigation DB. (We probably don't have to do anything for POST requests; I assume our controllers are smart enough OOTB to persist post meta data that they receive back to the DB.)
  2. We need the editor to persist the meta attached to a wp_navigation entity when that entity is saved. (This doesn't seem to be happening right now; I've some folks in Slack if this is possible.)

@ockham
Copy link
Contributor

ockham commented Jan 18, 2024

The following conditions detailed here loads menu items from an 'unstable location'. I'm not sure how easy it would be to track ignoredHookedBlocks for these menu types meaning we could end up hooking duplicate blocks into them.

One option would be to not hook them in at all, we can do this by checking if there is a navigation ID.

TBH this is something I wouldn't even tackle as part of this PR for the time being. I'm not too familiar with the Navigation block's fallback strategies, and I think it'll add quite a bit more complexity to figure out.

My suggestion would be to keep this one focused on the "basic" scenario; whatever solution we find for that will hopefully carry over to pretty much anything that involves loading from a DB record.

@tjcafferkey
Copy link
Contributor Author

tjcafferkey commented Jan 19, 2024

Unfortunately, I'm currently getting an error when saving after moving a hooked block:

@ockham I'm ashamed to say this was likely the result of some rushed programming. I've committed a fix here a5434ff which should fix that specific bug so I don't think it goes "deeper" as we may have feared but it would be great if you could re-test.

I've also updated the testing instructions on this PR to cover different methods of insertions & testing scenarios for my own sake.

@tjcafferkey
Copy link
Contributor Author

tjcafferkey commented Jan 19, 2024

@ockham can you elaborate on a few of the below points for me please:

We need to find a way to inject the meta at REST API controller stage (for GET requests), rather than actually creating it in the wp_navigation DB. (We probably don't have to do anything for POST requests; I assume our controllers are smart enough OOTB to persist post meta data that they receive back to the DB.)

As far as I was aware you did that when you registered the meta with the 'show_in_rest' => true argument? Do you mean you'd like to register and include the _wp_ignored_hooked_blocks meta data in all responses, for all entity requests without explicitly registering it on a per-entity basis (e.g. navigation) like you had done in your example above?

We need the editor to persist the meta attached to a wp_navigation entity when that entity is saved. (This doesn't seem to be happening right now; I've some folks in Slack if this is possible.)

I am assuming by this you mean when requests (e.g. when saving an entity in the editor) you want to meta data to be sent along with the request which the controller will handle and save in the database?

@ockham ockham marked this pull request as ready for review January 19, 2024 11:19
@tjcafferkey tjcafferkey marked this pull request as draft January 19, 2024 11:20
@tjcafferkey tjcafferkey marked this pull request as ready for review January 19, 2024 11:44
@ockham
Copy link
Contributor

ockham commented Jan 22, 2024

Unfortunately, I'm currently getting an error when saving after moving a hooked block:

@ockham I'm ashamed to say this was likely the result of some rushed programming. I've committed a fix here a5434ff which should fix that specific bug so I don't think it goes "deeper" as we may have feared but it would be great if you could re-test.

No worries! Glad it was an easy fix 😄

I've also updated the testing instructions on this PR to cover different methods of insertions & testing scenarios for my own sake.

Perfect, thank you!

@ockham
Copy link
Contributor

ockham commented Jan 22, 2024

We need to find a way to inject the meta at REST API controller stage (for GET requests), rather than actually creating it in the wp_navigation DB. (We probably don't have to do anything for POST requests; I assume our controllers are smart enough OOTB to persist post meta data that they receive back to the DB.)

As far as I was aware you did that when you registered the meta with the 'show_in_rest' => true argument? Do you mean you'd like to register and include the _wp_ignored_hooked_blocks meta data in all responses, for all entity requests without explicitly registering it on a per-entity basis (e.g. navigation) like you had done in your example above?

No, what I meant wasn't about registering the meta 😅 It was about sending it to the client (i.e. the editor), without simultaneously persisting it to the DB. This would mimic the "typical process", where we also send the markup with the ignoredHookedBlocks attribute set to the client (but don't set it in the DB just yet). The idea being that it's only set in the DB when the user makes a change in the editor that's saved back via the REST API.

(Apologies for the confusion! Plus we might not actually need to do anything of the sort if your PR works fine as-is 😊)

We need the editor to persist the meta attached to a wp_navigation entity when that entity is saved. (This doesn't seem to be happening right now; I've some folks in Slack if this is possible.)

I am assuming by this you mean when requests (e.g. when saving an entity in the editor) you want to meta data to be sent along with the request which the controller will handle and save in the database?

Yeah, exactly!

@ockham ockham force-pushed the experimental/update-wp-navigation-block-renderer-with-block-hooks branch from aeda9ea to bababb9 Compare January 24, 2024 16:28
@ockham
Copy link
Contributor

ockham commented Jan 24, 2024

Aaand we'll need to rebase: #57979

This moves the WP_Navigation_Block_Renderer back into the rendering file for the Navigation block

I've rebased, but we'll need to do some more tweaking 😕


if ( function_exists( 'get_hooked_blocks' ) ) {
// Run Block Hooks algorithm to inject hooked blocks.
$markup = gutenberg_insert_hooked_blocks_into_navigation_block( $blocks, $navigation_post );
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll need to change this function name: Dynamic blocks PHP -- i.e. pretty much any packages/block-library/src/*/*.php files -- cannot contain any gutenberg_ prefixed functions, as they are synced verbatim from GB to Core via npm package sync.

Copy link
Contributor

Choose a reason for hiding this comment

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

if ( function_exists( 'get_hooked_blocks' ) ) {
// Run Block Hooks algorithm to inject hooked blocks.
// We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
$markup = gutenberg_insert_hooked_blocks_into_navigation_block( $fallback_blocks, $navigation_post );
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@ockham
Copy link
Contributor

ockham commented Jan 24, 2024

I've renamed our functions following the convention and moved all code into packages/block-library/src/navigation/index.php -- also following convention 😅 (see #57979)

@ockham ockham merged commit a11d04f into WordPress:trunk Jan 24, 2024
53 checks passed
@github-actions github-actions bot added this to the Gutenberg 17.6 milestone Jan 24, 2024
@ockham
Copy link
Contributor

ockham commented Jan 25, 2024

Since all the code touched by this PR is inside packages/block-library/src/navigation/index.php, no code needs to be manually ported to Core, as that file is carried over automatically through npm package syncs.

Once that has happened, we should however update the $context arg PHPDoc in Block Hooks code in Core, as it can now also be a wp_navigation post object. PR here: WordPress/wordpress-develop#5946

@tjcafferkey
Copy link
Contributor Author

@ockham are we sure using get_hooked_blocks to feature gate the Navigation x Block Hooks PR is the right check? Since get_hooked_block_markup is the function that inserts the metadata ignoredHookedBlocks would that not make more sense to check against?

If I run WP 6.4.3 and Gutenberg trunk and test the following:

  1. Add the below code to your themes functions file to register the core/search block as an inner block
  2. Load header template part, change the width of the Search block and save template part
  3. Load frontend. Another search has been inserted (2 in total now) since it hasn’t got the ignoredHookedBlocks data
function register_navigation_last_child( $hooked_blocks, $position, $anchor_block, $context ) {
	if ( $anchor_block === 'core/navigation' && $position === 'last_child' ) {
		$hooked_blocks[] = 'core/search';
	}

	return $hooked_blocks;
}

add_filter( 'hooked_block_types', 'register_navigation_last_child', 10, 4 );

@ockham
Copy link
Contributor

ockham commented Jan 29, 2024

@tjcafferkey Ah, good spot!

I wanted to use get_hooked_blocks as feature gate, as that's been in Core since 6.4 -- so Block Hooks insertion into the Navigation block would work when used on that WP version. However, it's true that the whole ignoredHookedBlocks concept is more recent, so WP 6.4.x won't inject that 😕

Using get_hooked_block_markup as the gate instead seems like a good solution, as that correlates with when I introduced ignoredHookedBlocks. The downside is that this means that we won't get hooked blocks inserted into the Navigation block when run on WP 6.4.x (unless we backport the relevant code). That's still preferable over that bug, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Navigation Affects the Navigation Block [Feature] Extensibility The ability to extend blocks or the editing experience [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants