Diff: STRATO-apps/wordpress_03/app/wp-includes/class-wp-block.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * Blocks API: WP_Block class
4 + *
5 + * @package WordPress
6 + * @since 5.5.0
7 + */
8 +
9 + /**
10 + * Class representing a parsed instance of a block.
11 + *
12 + * @since 5.5.0
13 + * @property array $attributes
14 + */
15 + #[AllowDynamicProperties]
16 + class WP_Block {
17 +
18 + /**
19 + * Original parsed array representation of block.
20 + *
21 + * @since 5.5.0
22 + * @var array
23 + */
24 + public $parsed_block;
25 +
26 + /**
27 + * Name of block.
28 + *
29 + * @example "core/paragraph"
30 + *
31 + * @since 5.5.0
32 + * @var string|null
33 + */
34 + public $name;
35 +
36 + /**
37 + * Block type associated with the instance.
38 + *
39 + * @since 5.5.0
40 + * @var WP_Block_Type
41 + */
42 + public $block_type;
43 +
44 + /**
45 + * Block context values.
46 + *
47 + * @since 5.5.0
48 + * @var array
49 + */
50 + public $context = array();
51 +
52 + /**
53 + * All available context of the current hierarchy.
54 + *
55 + * @since 5.5.0
56 + * @var array
57 + */
58 + protected $available_context = array();
59 +
60 + /**
61 + * Block type registry.
62 + *
63 + * @since 5.9.0
64 + * @var WP_Block_Type_Registry
65 + */
66 + protected $registry;
67 +
68 + /**
69 + * List of inner blocks (of this same class)
70 + *
71 + * @since 5.5.0
72 + * @var WP_Block_List
73 + */
74 + public $inner_blocks = array();
75 +
76 + /**
77 + * Resultant HTML from inside block comment delimiters after removing inner
78 + * blocks.
79 + *
80 + * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..."
81 + *
82 + * @since 5.5.0
83 + * @var string
84 + */
85 + public $inner_html = '';
86 +
87 + /**
88 + * List of string fragments and null markers where inner blocks were found
89 + *
90 + * @example array(
91 + * 'inner_html' => 'BeforeInnerAfter',
92 + * 'inner_blocks' => array( block, block ),
93 + * 'inner_content' => array( 'Before', null, 'Inner', null, 'After' ),
94 + * )
95 + *
96 + * @since 5.5.0
97 + * @var array
98 + */
99 + public $inner_content = array();
100 +
101 + /**
102 + * Constructor.
103 + *
104 + * Populates object properties from the provided block instance argument.
105 + *
106 + * The given array of context values will not necessarily be available on
107 + * the instance itself, but is treated as the full set of values provided by
108 + * the block's ancestry. This is assigned to the private `available_context`
109 + * property. Only values which are configured to consumed by the block via
110 + * its registered type will be assigned to the block's `context` property.
111 + *
112 + * @since 5.5.0
113 + *
114 + * @param array $block {
115 + * An associative array of a single parsed block object. See WP_Block_Parser_Block.
116 + *
117 + * @type string|null $blockName Name of block.
118 + * @type array $attrs Attributes from block comment delimiters.
119 + * @type array $innerBlocks List of inner blocks. An array of arrays that
120 + * have the same structure as this one.
121 + * @type string $innerHTML HTML from inside block comment delimiters.
122 + * @type array $innerContent List of string fragments and null markers where inner blocks were found.
123 + * }
124 + * @param array $available_context Optional array of ancestry context values.
125 + * @param WP_Block_Type_Registry $registry Optional block type registry.
126 + */
127 + public function __construct( $block, $available_context = array(), $registry = null ) {
128 + $this->parsed_block = $block;
129 + $this->name = $block['blockName'];
130 +
131 + if ( is_null( $registry ) ) {
132 + $registry = WP_Block_Type_Registry::get_instance();
133 + }
134 +
135 + $this->registry = $registry;
136 +
137 + $this->block_type = $registry->get_registered( $this->name );
138 +
139 + $this->available_context = $available_context;
140 +
141 + $this->refresh_context_dependents();
142 + }
143 +
144 + /**
145 + * Updates the context for the current block and its inner blocks.
146 + *
147 + * The method updates the context of inner blocks, if any, by passing down
148 + * any context values the block provides (`provides_context`).
149 + *
150 + * If the block has inner blocks, the method recursively processes them by creating new instances of `WP_Block`
151 + * for each inner block and updating their context based on the block's `provides_context` property.
152 + *
153 + * @since 6.8.0
154 + */
155 + public function refresh_context_dependents() {
156 + /*
157 + * Merging the `$context` property here is not ideal, but for now needs to happen because of backward compatibility.
158 + * Ideally, the `$context` property itself would not be filterable directly and only the `$available_context` would be filterable.
159 + * However, this needs to be separately explored whether it's possible without breakage.
160 + */
161 + $this->available_context = array_merge( $this->available_context, $this->context );
162 +
163 + if ( ! empty( $this->block_type->uses_context ) ) {
164 + foreach ( $this->block_type->uses_context as $context_name ) {
165 + if ( array_key_exists( $context_name, $this->available_context ) ) {
166 + $this->context[ $context_name ] = $this->available_context[ $context_name ];
167 + }
168 + }
169 + }
170 +
171 + $this->refresh_parsed_block_dependents();
172 + }
173 +
174 + /**
175 + * Updates the parsed block content for the current block and its inner blocks.
176 + *
177 + * This method sets the `inner_html` and `inner_content` properties of the block based on the parsed
178 + * block content provided during initialization. It ensures that the block instance reflects the
179 + * most up-to-date content for both the inner HTML and any string fragments around inner blocks.
180 + *
181 + * If the block has inner blocks, this method initializes a new `WP_Block_List` for them, ensuring the
182 + * correct content and context are updated for each nested block.
183 + *
184 + * @since 6.8.0
185 + */
186 + public function refresh_parsed_block_dependents() {
187 + if ( ! empty( $this->parsed_block['innerBlocks'] ) ) {
188 + $child_context = $this->available_context;
189 +
190 + if ( ! empty( $this->block_type->provides_context ) ) {
191 + foreach ( $this->block_type->provides_context as $context_name => $attribute_name ) {
192 + if ( array_key_exists( $attribute_name, $this->attributes ) ) {
193 + $child_context[ $context_name ] = $this->attributes[ $attribute_name ];
194 + }
195 + }
196 + }
197 +
198 + $this->inner_blocks = new WP_Block_List( $this->parsed_block['innerBlocks'], $child_context, $this->registry );
199 + }
200 +
201 + if ( ! empty( $this->parsed_block['innerHTML'] ) ) {
202 + $this->inner_html = $this->parsed_block['innerHTML'];
203 + }
204 +
205 + if ( ! empty( $this->parsed_block['innerContent'] ) ) {
206 + $this->inner_content = $this->parsed_block['innerContent'];
207 + }
208 + }
209 +
210 + /**
211 + * Returns a value from an inaccessible property.
212 + *
213 + * This is used to lazily initialize the `attributes` property of a block,
214 + * such that it is only prepared with default attributes at the time that
215 + * the property is accessed. For all other inaccessible properties, a `null`
216 + * value is returned.
217 + *
218 + * @since 5.5.0
219 + *
220 + * @param string $name Property name.
221 + * @return array|null Prepared attributes, or null.
222 + */
223 + public function __get( $name ) {
224 + if ( 'attributes' === $name ) {
225 + $this->attributes = isset( $this->parsed_block['attrs'] ) ?
226 + $this->parsed_block['attrs'] :
227 + array();
228 +
229 + if ( ! is_null( $this->block_type ) ) {
230 + $this->attributes = $this->block_type->prepare_attributes_for_render( $this->attributes );
231 + }
232 +
233 + return $this->attributes;
234 + }
235 +
236 + return null;
237 + }
238 +
239 + /**
240 + * Processes the block bindings and updates the block attributes with the values from the sources.
241 + *
242 + * A block might contain bindings in its attributes. Bindings are mappings
243 + * between an attribute of the block and a source. A "source" is a function
244 + * registered with `register_block_bindings_source()` that defines how to
245 + * retrieve a value from outside the block, e.g. from post meta.
246 + *
247 + * This function will process those bindings and update the block's attributes
248 + * with the values coming from the bindings.
249 + *
250 + * ### Example
251 + *
252 + * The "bindings" property for an Image block might look like this:
253 + *
254 + * ```json
255 + * {
256 + * "metadata": {
257 + * "bindings": {
258 + * "title": {
259 + * "source": "core/post-meta",
260 + * "args": { "key": "text_custom_field" }
261 + * },
262 + * "url": {
263 + * "source": "core/post-meta",
264 + * "args": { "key": "url_custom_field" }
265 + * }
266 + * }
267 + * }
268 + * }
269 + * ```
270 + *
271 + * The above example will replace the `title` and `url` attributes of the Image
272 + * block with the values of the `text_custom_field` and `url_custom_field` post meta.
273 + *
274 + * @since 6.5.0
275 + * @since 6.6.0 Handle the `__default` attribute for pattern overrides.
276 + * @since 6.7.0 Return any updated bindings metadata in the computed attributes.
277 + *
278 + * @return array The computed block attributes for the provided block bindings.
279 + */
280 + private function process_block_bindings() {
281 + $block_type = $this->name;
282 + $parsed_block = $this->parsed_block;
283 + $computed_attributes = array();
284 + $supported_block_attributes = get_block_bindings_supported_attributes( $block_type );
285 +
286 + // If the block doesn't have the bindings property, isn't one of the supported
287 + // block types, or the bindings property is not an array, return the block content.
288 + if (
289 + empty( $supported_block_attributes ) ||
290 + empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
291 + ! is_array( $parsed_block['attrs']['metadata']['bindings'] )
292 + ) {
293 + return $computed_attributes;
294 + }
295 +
296 + $bindings = $parsed_block['attrs']['metadata']['bindings'];
297 +
298 + /*
299 + * If the default binding is set for pattern overrides, replace it
300 + * with a pattern override binding for all supported attributes.
301 + */
302 + if (
303 + isset( $bindings['__default']['source'] ) &&
304 + 'core/pattern-overrides' === $bindings['__default']['source']
305 + ) {
306 + $updated_bindings = array();
307 +
308 + /*
309 + * Build a binding array of all supported attributes.
310 + * Note that this also omits the `__default` attribute from the
311 + * resulting array.
312 + */
313 + foreach ( $supported_block_attributes as $attribute_name ) {
314 + // Retain any non-pattern override bindings that might be present.
315 + $updated_bindings[ $attribute_name ] = isset( $bindings[ $attribute_name ] )
316 + ? $bindings[ $attribute_name ]
317 + : array( 'source' => 'core/pattern-overrides' );
318 + }
319 + $bindings = $updated_bindings;
320 + /*
321 + * Update the bindings metadata of the computed attributes.
322 + * This ensures the block receives the expanded __default binding metadata when it renders.
323 + */
324 + $computed_attributes['metadata'] = array_merge(
325 + $parsed_block['attrs']['metadata'],
326 + array( 'bindings' => $bindings )
327 + );
328 + }
329 +
330 + foreach ( $bindings as $attribute_name => $block_binding ) {
331 + // If the attribute is not in the supported list, process next attribute.
332 + if ( ! in_array( $attribute_name, $supported_block_attributes, true ) ) {
333 + continue;
334 + }
335 + // If no source is provided, or that source is not registered, process next attribute.
336 + if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) {
337 + continue;
338 + }
339 +
340 + $block_binding_source = get_block_bindings_source( $block_binding['source'] );
341 + if ( null === $block_binding_source ) {
342 + continue;
343 + }
344 +
345 + // Adds the necessary context defined by the source.
346 + if ( ! empty( $block_binding_source->uses_context ) ) {
347 + foreach ( $block_binding_source->uses_context as $context_name ) {
348 + if ( array_key_exists( $context_name, $this->available_context ) ) {
349 + $this->context[ $context_name ] = $this->available_context[ $context_name ];
350 + }
351 + }
352 + }
353 +
354 + $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array();
355 + $source_value = $block_binding_source->get_value( $source_args, $this, $attribute_name );
356 +
357 + // If the value is not null, process the HTML based on the block and the attribute.
358 + if ( ! is_null( $source_value ) ) {
359 + $computed_attributes[ $attribute_name ] = $source_value;
360 + }
361 + }
362 +
363 + return $computed_attributes;
364 + }
365 +
366 + /**
367 + * Depending on the block attribute name, replace its value in the HTML based on the value provided.
368 + *
369 + * @since 6.5.0
370 + *
371 + * @param string $block_content Block content.
372 + * @param string $attribute_name The attribute name to replace.
373 + * @param mixed $source_value The value used to replace in the HTML.
374 + * @return string The modified block content.
375 + */
376 + private function replace_html( string $block_content, string $attribute_name, $source_value ) {
377 + $block_type = $this->block_type;
378 + if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) {
379 + return $block_content;
380 + }
381 +
382 + // Depending on the attribute source, the processing will be different.
383 + switch ( $block_type->attributes[ $attribute_name ]['source'] ) {
384 + case 'html':
385 + case 'rich-text':
386 + $block_reader = self::get_block_bindings_processor( $block_content );
387 +
388 + // TODO: Support for CSS selectors whenever they are ready in the HTML API.
389 + // In the meantime, support comma-separated selectors by exploding them into an array.
390 + $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] );
391 + // Add a bookmark to the first tag to be able to iterate over the selectors.
392 + $block_reader->next_tag();
393 + $block_reader->set_bookmark( 'iterate-selectors' );
394 +
395 + foreach ( $selectors as $selector ) {
396 + // If the parent tag, or any of its children, matches the selector, replace the HTML.
397 + if ( strcasecmp( $block_reader->get_tag(), $selector ) === 0 || $block_reader->next_tag(
398 + array(
399 + 'tag_name' => $selector,
400 + )
401 + ) ) {
402 + // TODO: Use `WP_HTML_Processor::set_inner_html` method once it's available.
403 + $block_reader->release_bookmark( 'iterate-selectors' );
404 + $block_reader->replace_rich_text( wp_kses_post( $source_value ) );
405 + return $block_reader->get_updated_html();
406 + } else {
407 + $block_reader->seek( 'iterate-selectors' );
408 + }
409 + }
410 + $block_reader->release_bookmark( 'iterate-selectors' );
411 + return $block_content;
412 +
413 + case 'attribute':
414 + $amended_content = new WP_HTML_Tag_Processor( $block_content );
415 + if ( ! $amended_content->next_tag(
416 + array(
417 + // TODO: build the query from CSS selector.
418 + 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'],
419 + )
420 + ) ) {
421 + return $block_content;
422 + }
423 + $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value );
424 + return $amended_content->get_updated_html();
425 +
426 + default:
427 + return $block_content;
428 + }
429 + }
430 +
431 + private static function get_block_bindings_processor( string $block_content ) {
432 + $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor {
433 + /**
434 + * Replace the rich text content between a tag opener and matching closer.
435 + *
436 + * When stopped on a tag opener, replace the content enclosed by it and its
437 + * matching closer with the provided rich text.
438 + *
439 + * @param string $rich_text The rich text to replace the original content with.
440 + * @return bool True on success.
441 + */
442 + public function replace_rich_text( $rich_text ) {
443 + if ( $this->is_tag_closer() || ! $this->expects_closer() ) {
444 + return false;
445 + }
446 +
447 + $depth = $this->get_current_depth();
448 + $tag_name = $this->get_tag();
449 +
450 + $this->set_bookmark( '_wp_block_bindings' );
451 + // The bookmark names are prefixed with `_` so the key below has an extra `_`.
452 + $tag_opener = $this->bookmarks['__wp_block_bindings'];
453 + $start = $tag_opener->start + $tag_opener->length;
454 +
455 + // Find matching tag closer.
456 + while ( $this->next_token() && $this->get_current_depth() >= $depth ) {
457 + }
458 +
459 + if ( ! $this->is_tag_closer() || $tag_name !== $this->get_tag() ) {
460 + return false;
461 + }
462 +
463 + $this->set_bookmark( '_wp_block_bindings' );
464 + $tag_closer = $this->bookmarks['__wp_block_bindings'];
465 + $end = $tag_closer->start;
466 +
467 + $this->lexical_updates[] = new WP_HTML_Text_Replacement(
468 + $start,
469 + $end - $start,
470 + $rich_text
471 + );
472 +
473 + return true;
474 + }
475 + };
476 +
477 + return $internal_processor_class::create_fragment( $block_content );
478 + }
479 +
480 + /**
481 + * Generates the render output for the block.
482 + *
483 + * @since 5.5.0
484 + * @since 6.5.0 Added block bindings processing.
485 + *
486 + * @global WP_Post $post Global post object.
487 + *
488 + * @param array $options {
489 + * Optional options object.
490 + *
491 + * @type bool $dynamic Defaults to 'true'. Optionally set to false to avoid using the block's render_callback.
492 + * }
493 + * @return string Rendered block output.
494 + */
495 + public function render( $options = array() ) {
496 + global $post;
497 +
498 + $before_wp_enqueue_scripts_count = did_action( 'wp_enqueue_scripts' );
499 +
500 + // Capture the current assets queues.
501 + $before_styles_queue = wp_styles()->queue;
502 + $before_scripts_queue = wp_scripts()->queue;
503 + $before_script_modules_queue = wp_script_modules()->get_queue();
504 +
505 + /*
506 + * There can be only one root interactive block at a time because the rendered HTML of that block contains
507 + * the rendered HTML of all its inner blocks, including any interactive block.
508 + */
509 + static $root_interactive_block = null;
510 + /**
511 + * Filters whether Interactivity API should process directives.
512 + *
513 + * @since 6.6.0
514 + *
515 + * @param bool $enabled Whether the directives processing is enabled.
516 + */
517 + $interactivity_process_directives_enabled = apply_filters( 'interactivity_process_directives', true );
518 + if (
519 + $interactivity_process_directives_enabled && null === $root_interactive_block && (
520 + ( isset( $this->block_type->supports['interactivity'] ) && true === $this->block_type->supports['interactivity'] ) ||
521 + ! empty( $this->block_type->supports['interactivity']['interactive'] )
522 + )
523 + ) {
524 + $root_interactive_block = $this;
525 + }
526 +
527 + $options = wp_parse_args(
528 + $options,
529 + array(
530 + 'dynamic' => true,
531 + )
532 + );
533 +
534 + // Process the block bindings and get attributes updated with the values from the sources.
535 + $computed_attributes = $this->process_block_bindings();
536 + if ( ! empty( $computed_attributes ) ) {
537 + // Merge the computed attributes with the original attributes.
538 + $this->attributes = array_merge( $this->attributes, $computed_attributes );
539 + }
540 +
541 + $is_dynamic = $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic();
542 + $block_content = '';
543 +
544 + if ( ! $options['dynamic'] || empty( $this->block_type->skip_inner_blocks ) ) {
545 + $index = 0;
546 +
547 + foreach ( $this->inner_content as $chunk ) {
548 + if ( is_string( $chunk ) ) {
549 + $block_content .= $chunk;
550 + } else {
551 + $inner_block = $this->inner_blocks[ $index ];
552 + $parent_block = $this;
553 +
554 + /** This filter is documented in wp-includes/blocks.php */
555 + $pre_render = apply_filters( 'pre_render_block', null, $inner_block->parsed_block, $parent_block );
556 +
557 + if ( ! is_null( $pre_render ) ) {
558 + $block_content .= $pre_render;
559 + } else {
560 + $source_block = $inner_block->parsed_block;
561 + $inner_block_context = $inner_block->context;
562 +
563 + /** This filter is documented in wp-includes/blocks.php */
564 + $inner_block->parsed_block = apply_filters( 'render_block_data', $inner_block->parsed_block, $source_block, $parent_block );
565 +
566 + /** This filter is documented in wp-includes/blocks.php */
567 + $inner_block->context = apply_filters( 'render_block_context', $inner_block->context, $inner_block->parsed_block, $parent_block );
568 +
569 + /*
570 + * The `refresh_context_dependents()` method already calls `refresh_parsed_block_dependents()`.
571 + * Therefore the second condition is irrelevant if the first one is satisfied.
572 + */
573 + if ( $inner_block->context !== $inner_block_context ) {
574 + $inner_block->refresh_context_dependents();
575 + } elseif ( $inner_block->parsed_block !== $source_block ) {
576 + $inner_block->refresh_parsed_block_dependents();
577 + }
578 +
579 + $block_content .= $inner_block->render();
580 + }
581 +
582 + ++$index;
583 + }
584 + }
585 + }
586 +
587 + if ( ! empty( $computed_attributes ) && ! empty( $block_content ) ) {
588 + foreach ( $computed_attributes as $attribute_name => $source_value ) {
589 + $block_content = $this->replace_html( $block_content, $attribute_name, $source_value );
590 + }
591 + }
592 +
593 + if ( $is_dynamic ) {
594 + $global_post = $post;
595 + $parent = WP_Block_Supports::$block_to_render;
596 +
597 + WP_Block_Supports::$block_to_render = $this->parsed_block;
598 +
599 + $block_content = (string) call_user_func( $this->block_type->render_callback, $this->attributes, $block_content, $this );
600 +
601 + WP_Block_Supports::$block_to_render = $parent;
602 +
603 + $post = $global_post;
604 + }
605 +
606 + if ( ( ! empty( $this->block_type->script_handles ) ) ) {
607 + foreach ( $this->block_type->script_handles as $script_handle ) {
608 + wp_enqueue_script( $script_handle );
609 + }
610 + }
611 +
612 + if ( ! empty( $this->block_type->view_script_handles ) ) {
613 + foreach ( $this->block_type->view_script_handles as $view_script_handle ) {
614 + wp_enqueue_script( $view_script_handle );
615 + }
616 + }
617 +
618 + if ( ! empty( $this->block_type->view_script_module_ids ) ) {
619 + foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) {
620 + wp_enqueue_script_module( $view_script_module_id );
621 + }
622 + }
623 +
624 + /*
625 + * For Core blocks, these styles are only enqueued if `wp_should_load_separate_core_block_assets()` returns
626 + * true. Otherwise these `wp_enqueue_style()` calls will not have any effect, as the Core blocks are relying on
627 + * the combined 'wp-block-library' stylesheet instead, which is unconditionally enqueued.
628 + */
629 + if ( ( ! empty( $this->block_type->style_handles ) ) ) {
630 + foreach ( $this->block_type->style_handles as $style_handle ) {
631 + wp_enqueue_style( $style_handle );
632 + }
633 + }
634 +
635 + if ( ( ! empty( $this->block_type->view_style_handles ) ) ) {
636 + foreach ( $this->block_type->view_style_handles as $view_style_handle ) {
637 + wp_enqueue_style( $view_style_handle );
638 + }
639 + }
640 +
641 + /**
642 + * Filters the content of a single block.
643 + *
644 + * @since 5.0.0
645 + * @since 5.9.0 The `$instance` parameter was added.
646 + *
647 + * @param string $block_content The block content.
648 + * @param array $block The full block, including name and attributes.
649 + * @param WP_Block $instance The block instance.
650 + */
651 + $block_content = apply_filters( 'render_block', $block_content, $this->parsed_block, $this );
652 +
653 + /**
654 + * Filters the content of a single block.
655 + *
656 + * The dynamic portion of the hook name, `$name`, refers to
657 + * the block name, e.g. "core/paragraph".
658 + *
659 + * @since 5.7.0
660 + * @since 5.9.0 The `$instance` parameter was added.
661 + *
662 + * @param string $block_content The block content.
663 + * @param array $block The full block, including name and attributes.
664 + * @param WP_Block $instance The block instance.
665 + */
666 + $block_content = apply_filters( "render_block_{$this->name}", $block_content, $this->parsed_block, $this );
667 +
668 + if ( $root_interactive_block === $this ) {
669 + // The root interactive block has finished rendering. Time to process directives.
670 + $block_content = wp_interactivity_process_directives( $block_content );
671 + $root_interactive_block = null;
672 + }
673 +
674 + // Capture the new assets enqueued during rendering, and restore the queues the state prior to rendering.
675 + $after_styles_queue = wp_styles()->queue;
676 + $after_scripts_queue = wp_scripts()->queue;
677 + $after_script_modules_queue = wp_script_modules()->get_queue();
678 +
679 + /*
680 + * As a very special case, a dynamic block may in fact include a call to wp_head() (and thus wp_enqueue_scripts()),
681 + * in which all of its enqueued assets are targeting wp_footer. In this case, nothing would be printed, but this
682 + * shouldn't indicate that the just-enqueued assets should be dequeued due to it being an empty block.
683 + */
684 + $just_did_wp_enqueue_scripts = ( did_action( 'wp_enqueue_scripts' ) !== $before_wp_enqueue_scripts_count );
685 +
686 + $has_new_styles = ( $before_styles_queue !== $after_styles_queue );
687 + $has_new_scripts = ( $before_scripts_queue !== $after_scripts_queue );
688 + $has_new_script_modules = ( $before_script_modules_queue !== $after_script_modules_queue );
689 +
690 + // Dequeue the newly enqueued assets with the existing assets if the rendered block was empty & wp_enqueue_scripts did not fire.
691 + if (
692 + ! $just_did_wp_enqueue_scripts &&
693 + ( $has_new_styles || $has_new_scripts || $has_new_script_modules ) &&
694 + (
695 + trim( $block_content ) === '' &&
696 + /**
697 + * Filters whether to enqueue assets for a block which has no rendered content.
698 + *
699 + * @since 6.9.0
700 + *
701 + * @param bool $enqueue Whether to enqueue assets.
702 + * @param string $block_name Block name.
703 + */
704 + ! (bool) apply_filters( 'enqueue_empty_block_content_assets', false, $this->name )
705 + )
706 + ) {
707 + foreach ( array_diff( $after_styles_queue, $before_styles_queue ) as $handle ) {
708 + wp_dequeue_style( $handle );
709 + }
710 + foreach ( array_diff( $after_scripts_queue, $before_scripts_queue ) as $handle ) {
711 + wp_dequeue_script( $handle );
712 + }
713 + foreach ( array_diff( $after_script_modules_queue, $before_script_modules_queue ) as $handle ) {
714 + wp_dequeue_script_module( $handle );
715 + }
716 + }
717 +
718 + return $block_content;
719 + }
720 + }
721 +