@@ -1140,6 +1140,106 @@ function apply_block_hooks_to_content( $content, $context = null, $callback = 'i
11401140 return $ content ;
11411141}
11421142
1143+ /**
1144+ * Run the Block Hooks algorithm on a post object's content.
1145+ *
1146+ * This function is different from `apply_block_hooks_to_content` in that
1147+ * it takes ignored hooked block information from the post's metadata into
1148+ * account. This ensures that any blocks hooked as first or last child
1149+ * of the block that corresponds to the post type are handled correctly.
1150+ *
1151+ * @since 6.8.0
1152+ * @access private
1153+ *
1154+ * @param string $content Serialized content.
1155+ * @param WP_Post|null $post A post object that the content belongs to. If set to `null`,
1156+ * `get_post()` will be called to use the current post as context.
1157+ * Default: `null`.
1158+ * @param callable $callback A function that will be called for each block to generate
1159+ * the markup for a given list of blocks that are hooked to it.
1160+ * Default: 'insert_hooked_blocks'.
1161+ * @return string The serialized markup.
1162+ */
1163+ function apply_block_hooks_to_content_from_post_object ( $ content , WP_Post $ post = null , $ callback = 'insert_hooked_blocks ' ) {
1164+ // Default to the current post if no context is provided.
1165+ if ( null === $ post ) {
1166+ $ post = get_post ();
1167+ }
1168+
1169+ if ( ! $ post instanceof WP_Post ) {
1170+ return apply_block_hooks_to_content ( $ content , $ post , $ callback );
1171+ }
1172+
1173+ /*
1174+ * If the content was created using the classic editor or using a single Classic block
1175+ * (`core/freeform`), it might not contain any block markup at all.
1176+ * However, we still might need to inject hooked blocks in the first child or last child
1177+ * positions of the parent block. To be able to apply the Block Hooks algorithm, we wrap
1178+ * the content in a `core/freeform` wrapper block.
1179+ */
1180+ if ( ! has_blocks ( $ content ) ) {
1181+ $ original_content = $ content ;
1182+
1183+ $ content_wrapped_in_classic_block = get_comment_delimited_block_content (
1184+ 'core/freeform ' ,
1185+ array (),
1186+ $ content
1187+ );
1188+
1189+ $ content = $ content_wrapped_in_classic_block ;
1190+ }
1191+
1192+ $ attributes = array ();
1193+
1194+ // If context is a post object, `ignoredHookedBlocks` information is stored in its post meta.
1195+ $ ignored_hooked_blocks = get_post_meta ( $ post ->ID , '_wp_ignored_hooked_blocks ' , true );
1196+ if ( ! empty ( $ ignored_hooked_blocks ) ) {
1197+ $ ignored_hooked_blocks = json_decode ( $ ignored_hooked_blocks , true );
1198+ $ attributes ['metadata ' ] = array (
1199+ 'ignoredHookedBlocks ' => $ ignored_hooked_blocks ,
1200+ );
1201+ }
1202+
1203+ /*
1204+ * We need to wrap the content in a temporary wrapper block with that metadata
1205+ * so the Block Hooks algorithm can insert blocks that are hooked as first or last child
1206+ * of the wrapper block.
1207+ * To that end, we need to determine the wrapper block type based on the post type.
1208+ */
1209+ if ( 'wp_navigation ' === $ post ->post_type ) {
1210+ $ wrapper_block_type = 'core/navigation ' ;
1211+ } elseif ( 'wp_block ' === $ post ->post_type ) {
1212+ $ wrapper_block_type = 'core/block ' ;
1213+ } else {
1214+ $ wrapper_block_type = 'core/post-content ' ;
1215+ }
1216+
1217+ $ content = get_comment_delimited_block_content (
1218+ $ wrapper_block_type ,
1219+ $ attributes ,
1220+ $ content
1221+ );
1222+
1223+ // Apply Block Hooks.
1224+ $ content = apply_block_hooks_to_content ( $ content , $ post , $ callback );
1225+
1226+ // Finally, we need to remove the temporary wrapper block.
1227+ $ content = remove_serialized_parent_block ( $ content );
1228+
1229+ // If we wrapped the content in a `core/freeform` block, we also need to remove that.
1230+ if ( ! empty ( $ content_wrapped_in_classic_block ) ) {
1231+ /*
1232+ * We cannot simply use remove_serialized_parent_block() here,
1233+ * as that function assumes that the block wrapper is at the top level.
1234+ * However, there might now be a hooked block inserted next to it
1235+ * (as first or last child of the parent).
1236+ */
1237+ $ content = str_replace ( $ content_wrapped_in_classic_block , $ original_content , $ content );
1238+ }
1239+
1240+ return $ content ;
1241+ }
1242+
11431243/**
11441244 * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
11451245 *
@@ -1297,57 +1397,32 @@ function insert_hooked_blocks_into_rest_response( $response, $post ) {
12971397 return $ response ;
12981398 }
12991399
1300- $ attributes = array ();
1301- $ ignored_hooked_blocks = get_post_meta ( $ post ->ID , '_wp_ignored_hooked_blocks ' , true );
1302- if ( ! empty ( $ ignored_hooked_blocks ) ) {
1303- $ ignored_hooked_blocks = json_decode ( $ ignored_hooked_blocks , true );
1304- $ attributes ['metadata ' ] = array (
1305- 'ignoredHookedBlocks ' => $ ignored_hooked_blocks ,
1306- );
1307- }
1308-
1309- if ( 'wp_navigation ' === $ post ->post_type ) {
1310- $ wrapper_block_type = 'core/navigation ' ;
1311- } elseif ( 'wp_block ' === $ post ->post_type ) {
1312- $ wrapper_block_type = 'core/block ' ;
1313- } else {
1314- $ wrapper_block_type = 'core/post-content ' ;
1315- }
1316-
1317- $ content = get_comment_delimited_block_content (
1318- $ wrapper_block_type ,
1319- $ attributes ,
1320- $ response ->data ['content ' ]['raw ' ]
1321- );
1322-
1323- $ content = apply_block_hooks_to_content (
1324- $ content ,
1400+ $ response ->data ['content ' ]['raw ' ] = apply_block_hooks_to_content_from_post_object (
1401+ $ response ->data ['content ' ]['raw ' ],
13251402 $ post ,
13261403 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata '
13271404 );
13281405
1329- // Remove mock block wrapper.
1330- $ content = remove_serialized_parent_block ( $ content );
1331-
1332- $ response ->data ['content ' ]['raw ' ] = $ content ;
1333-
13341406 // If the rendered content was previously empty, we leave it like that.
13351407 if ( empty ( $ response ->data ['content ' ]['rendered ' ] ) ) {
13361408 return $ response ;
13371409 }
13381410
13391411 // `apply_block_hooks_to_content` is called above. Ensure it is not called again as a filter.
1340- $ priority = has_filter ( 'the_content ' , 'apply_block_hooks_to_content ' );
1412+ $ priority = has_filter ( 'the_content ' , 'apply_block_hooks_to_content_from_post_object ' );
13411413 if ( false !== $ priority ) {
1342- remove_filter ( 'the_content ' , 'apply_block_hooks_to_content ' , $ priority );
1414+ remove_filter ( 'the_content ' , 'apply_block_hooks_to_content_from_post_object ' , $ priority );
13431415 }
13441416
13451417 /** This filter is documented in wp-includes/post-template.php */
1346- $ response ->data ['content ' ]['rendered ' ] = apply_filters ( 'the_content ' , $ content );
1418+ $ response ->data ['content ' ]['rendered ' ] = apply_filters (
1419+ 'the_content ' ,
1420+ $ response ->data ['content ' ]['raw ' ]
1421+ );
13471422
13481423 // Restore the filter if it was set initially.
13491424 if ( false !== $ priority ) {
1350- add_filter ( 'the_content ' , 'apply_block_hooks_to_content ' , $ priority );
1425+ add_filter ( 'the_content ' , 'apply_block_hooks_to_content_from_post_object ' , $ priority );
13511426 }
13521427
13531428 return $ response ;
0 commit comments