189 |
199 |
190 return null; |
200 return null; |
191 } |
201 } |
192 |
202 |
193 /** |
203 /** |
|
204 * Processes the block bindings and updates the block attributes with the values from the sources. |
|
205 * |
|
206 * A block might contain bindings in its attributes. Bindings are mappings |
|
207 * between an attribute of the block and a source. A "source" is a function |
|
208 * registered with `register_block_bindings_source()` that defines how to |
|
209 * retrieve a value from outside the block, e.g. from post meta. |
|
210 * |
|
211 * This function will process those bindings and update the block's attributes |
|
212 * with the values coming from the bindings. |
|
213 * |
|
214 * ### Example |
|
215 * |
|
216 * The "bindings" property for an Image block might look like this: |
|
217 * |
|
218 * ```json |
|
219 * { |
|
220 * "metadata": { |
|
221 * "bindings": { |
|
222 * "title": { |
|
223 * "source": "core/post-meta", |
|
224 * "args": { "key": "text_custom_field" } |
|
225 * }, |
|
226 * "url": { |
|
227 * "source": "core/post-meta", |
|
228 * "args": { "key": "url_custom_field" } |
|
229 * } |
|
230 * } |
|
231 * } |
|
232 * } |
|
233 * ``` |
|
234 * |
|
235 * The above example will replace the `title` and `url` attributes of the Image |
|
236 * block with the values of the `text_custom_field` and `url_custom_field` post meta. |
|
237 * |
|
238 * @since 6.5.0 |
|
239 * @since 6.6.0 Handle the `__default` attribute for pattern overrides. |
|
240 * |
|
241 * @return array The computed block attributes for the provided block bindings. |
|
242 */ |
|
243 private function process_block_bindings() { |
|
244 $parsed_block = $this->parsed_block; |
|
245 $computed_attributes = array(); |
|
246 $supported_block_attributes = array( |
|
247 'core/paragraph' => array( 'content' ), |
|
248 'core/heading' => array( 'content' ), |
|
249 'core/image' => array( 'id', 'url', 'title', 'alt' ), |
|
250 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), |
|
251 ); |
|
252 |
|
253 // If the block doesn't have the bindings property, isn't one of the supported |
|
254 // block types, or the bindings property is not an array, return the block content. |
|
255 if ( |
|
256 ! isset( $supported_block_attributes[ $this->name ] ) || |
|
257 empty( $parsed_block['attrs']['metadata']['bindings'] ) || |
|
258 ! is_array( $parsed_block['attrs']['metadata']['bindings'] ) |
|
259 ) { |
|
260 return $computed_attributes; |
|
261 } |
|
262 |
|
263 $bindings = $parsed_block['attrs']['metadata']['bindings']; |
|
264 |
|
265 /* |
|
266 * If the default binding is set for pattern overrides, replace it |
|
267 * with a pattern override binding for all supported attributes. |
|
268 */ |
|
269 if ( |
|
270 isset( $bindings['__default']['source'] ) && |
|
271 'core/pattern-overrides' === $bindings['__default']['source'] |
|
272 ) { |
|
273 $updated_bindings = array(); |
|
274 |
|
275 /* |
|
276 * Build a binding array of all supported attributes. |
|
277 * Note that this also omits the `__default` attribute from the |
|
278 * resulting array. |
|
279 */ |
|
280 foreach ( $supported_block_attributes[ $parsed_block['blockName'] ] as $attribute_name ) { |
|
281 // Retain any non-pattern override bindings that might be present. |
|
282 $updated_bindings[ $attribute_name ] = isset( $bindings[ $attribute_name ] ) |
|
283 ? $bindings[ $attribute_name ] |
|
284 : array( 'source' => 'core/pattern-overrides' ); |
|
285 } |
|
286 $bindings = $updated_bindings; |
|
287 } |
|
288 |
|
289 foreach ( $bindings as $attribute_name => $block_binding ) { |
|
290 // If the attribute is not in the supported list, process next attribute. |
|
291 if ( ! in_array( $attribute_name, $supported_block_attributes[ $this->name ], true ) ) { |
|
292 continue; |
|
293 } |
|
294 // If no source is provided, or that source is not registered, process next attribute. |
|
295 if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) { |
|
296 continue; |
|
297 } |
|
298 |
|
299 $block_binding_source = get_block_bindings_source( $block_binding['source'] ); |
|
300 if ( null === $block_binding_source ) { |
|
301 continue; |
|
302 } |
|
303 |
|
304 $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array(); |
|
305 $source_value = $block_binding_source->get_value( $source_args, $this, $attribute_name ); |
|
306 |
|
307 // If the value is not null, process the HTML based on the block and the attribute. |
|
308 if ( ! is_null( $source_value ) ) { |
|
309 $computed_attributes[ $attribute_name ] = $source_value; |
|
310 } |
|
311 } |
|
312 |
|
313 return $computed_attributes; |
|
314 } |
|
315 |
|
316 /** |
|
317 * Depending on the block attribute name, replace its value in the HTML based on the value provided. |
|
318 * |
|
319 * @since 6.5.0 |
|
320 * |
|
321 * @param string $block_content Block content. |
|
322 * @param string $attribute_name The attribute name to replace. |
|
323 * @param mixed $source_value The value used to replace in the HTML. |
|
324 * @return string The modified block content. |
|
325 */ |
|
326 private function replace_html( string $block_content, string $attribute_name, $source_value ) { |
|
327 $block_type = $this->block_type; |
|
328 if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { |
|
329 return $block_content; |
|
330 } |
|
331 |
|
332 // Depending on the attribute source, the processing will be different. |
|
333 switch ( $block_type->attributes[ $attribute_name ]['source'] ) { |
|
334 case 'html': |
|
335 case 'rich-text': |
|
336 $block_reader = new WP_HTML_Tag_Processor( $block_content ); |
|
337 |
|
338 // TODO: Support for CSS selectors whenever they are ready in the HTML API. |
|
339 // In the meantime, support comma-separated selectors by exploding them into an array. |
|
340 $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] ); |
|
341 // Add a bookmark to the first tag to be able to iterate over the selectors. |
|
342 $block_reader->next_tag(); |
|
343 $block_reader->set_bookmark( 'iterate-selectors' ); |
|
344 |
|
345 // TODO: This shouldn't be needed when the `set_inner_html` function is ready. |
|
346 // Store the parent tag and its attributes to be able to restore them later in the button. |
|
347 // The button block has a wrapper while the paragraph and heading blocks don't. |
|
348 if ( 'core/button' === $this->name ) { |
|
349 $button_wrapper = $block_reader->get_tag(); |
|
350 $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); |
|
351 $button_wrapper_attrs = array(); |
|
352 foreach ( $button_wrapper_attribute_names as $name ) { |
|
353 $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); |
|
354 } |
|
355 } |
|
356 |
|
357 foreach ( $selectors as $selector ) { |
|
358 // If the parent tag, or any of its children, matches the selector, replace the HTML. |
|
359 if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( |
|
360 array( |
|
361 'tag_name' => $selector, |
|
362 ) |
|
363 ) ) { |
|
364 $block_reader->release_bookmark( 'iterate-selectors' ); |
|
365 |
|
366 // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. |
|
367 // Until then, it is hardcoded for the paragraph, heading, and button blocks. |
|
368 // Store the tag and its attributes to be able to restore them later. |
|
369 $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); |
|
370 $selector_attrs = array(); |
|
371 foreach ( $selector_attribute_names as $name ) { |
|
372 $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); |
|
373 } |
|
374 $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . "</$selector>"; |
|
375 $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); |
|
376 $amended_content->next_tag(); |
|
377 foreach ( $selector_attrs as $attribute_key => $attribute_value ) { |
|
378 $amended_content->set_attribute( $attribute_key, $attribute_value ); |
|
379 } |
|
380 if ( 'core/paragraph' === $this->name || 'core/heading' === $this->name ) { |
|
381 return $amended_content->get_updated_html(); |
|
382 } |
|
383 if ( 'core/button' === $this->name ) { |
|
384 $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}</$button_wrapper>"; |
|
385 $amended_button = new WP_HTML_Tag_Processor( $button_markup ); |
|
386 $amended_button->next_tag(); |
|
387 foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { |
|
388 $amended_button->set_attribute( $attribute_key, $attribute_value ); |
|
389 } |
|
390 return $amended_button->get_updated_html(); |
|
391 } |
|
392 } else { |
|
393 $block_reader->seek( 'iterate-selectors' ); |
|
394 } |
|
395 } |
|
396 $block_reader->release_bookmark( 'iterate-selectors' ); |
|
397 return $block_content; |
|
398 |
|
399 case 'attribute': |
|
400 $amended_content = new WP_HTML_Tag_Processor( $block_content ); |
|
401 if ( ! $amended_content->next_tag( |
|
402 array( |
|
403 // TODO: build the query from CSS selector. |
|
404 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'], |
|
405 ) |
|
406 ) ) { |
|
407 return $block_content; |
|
408 } |
|
409 $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value ); |
|
410 return $amended_content->get_updated_html(); |
|
411 |
|
412 default: |
|
413 return $block_content; |
|
414 } |
|
415 } |
|
416 |
|
417 |
|
418 /** |
194 * Generates the render output for the block. |
419 * Generates the render output for the block. |
195 * |
420 * |
196 * @since 5.5.0 |
421 * @since 5.5.0 |
|
422 * @since 6.5.0 Added block bindings processing. |
|
423 * |
|
424 * @global WP_Post $post Global post object. |
197 * |
425 * |
198 * @param array $options { |
426 * @param array $options { |
199 * Optional options object. |
427 * Optional options object. |
200 * |
428 * |
201 * @type bool $dynamic Defaults to 'true'. Optionally set to false to avoid using the block's render_callback. |
429 * @type bool $dynamic Defaults to 'true'. Optionally set to false to avoid using the block's render_callback. |
202 * } |
430 * } |
203 * @return string Rendered block output. |
431 * @return string Rendered block output. |
204 */ |
432 */ |
205 public function render( $options = array() ) { |
433 public function render( $options = array() ) { |
206 global $post; |
434 global $post; |
|
435 |
|
436 /* |
|
437 * There can be only one root interactive block at a time because the rendered HTML of that block contains |
|
438 * the rendered HTML of all its inner blocks, including any interactive block. |
|
439 */ |
|
440 static $root_interactive_block = null; |
|
441 /** |
|
442 * Filters whether Interactivity API should process directives. |
|
443 * |
|
444 * @since 6.6.0 |
|
445 * |
|
446 * @param bool $enabled Whether the directives processing is enabled. |
|
447 */ |
|
448 $interactivity_process_directives_enabled = apply_filters( 'interactivity_process_directives', true ); |
|
449 if ( |
|
450 $interactivity_process_directives_enabled && null === $root_interactive_block && ( |
|
451 ( isset( $this->block_type->supports['interactivity'] ) && true === $this->block_type->supports['interactivity'] ) || |
|
452 ! empty( $this->block_type->supports['interactivity']['interactive'] ) |
|
453 ) |
|
454 ) { |
|
455 $root_interactive_block = $this; |
|
456 } |
|
457 |
207 $options = wp_parse_args( |
458 $options = wp_parse_args( |
208 $options, |
459 $options, |
209 array( |
460 array( |
210 'dynamic' => true, |
461 'dynamic' => true, |
211 ) |
462 ) |
212 ); |
463 ); |
213 |
464 |
|
465 // Process the block bindings and get attributes updated with the values from the sources. |
|
466 $computed_attributes = $this->process_block_bindings(); |
|
467 if ( ! empty( $computed_attributes ) ) { |
|
468 // Merge the computed attributes with the original attributes. |
|
469 $this->attributes = array_merge( $this->attributes, $computed_attributes ); |
|
470 } |
|
471 |
214 $is_dynamic = $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic(); |
472 $is_dynamic = $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic(); |
215 $block_content = ''; |
473 $block_content = ''; |
216 |
474 |
217 if ( ! $options['dynamic'] || empty( $this->block_type->skip_inner_blocks ) ) { |
475 if ( ! $options['dynamic'] || empty( $this->block_type->skip_inner_blocks ) ) { |
218 $index = 0; |
476 $index = 0; |
257 WP_Block_Supports::$block_to_render = $parent; |
521 WP_Block_Supports::$block_to_render = $parent; |
258 |
522 |
259 $post = $global_post; |
523 $post = $global_post; |
260 } |
524 } |
261 |
525 |
262 if ( ! empty( $this->block_type->script ) ) { |
526 if ( ( ! empty( $this->block_type->script_handles ) ) ) { |
263 wp_enqueue_script( $this->block_type->script ); |
527 foreach ( $this->block_type->script_handles as $script_handle ) { |
264 } |
528 wp_enqueue_script( $script_handle ); |
265 |
529 } |
266 if ( ! empty( $this->block_type->view_script ) && empty( $this->block_type->render_callback ) ) { |
530 } |
267 wp_enqueue_script( $this->block_type->view_script ); |
531 |
268 } |
532 if ( ! empty( $this->block_type->view_script_handles ) ) { |
269 |
533 foreach ( $this->block_type->view_script_handles as $view_script_handle ) { |
270 if ( ! empty( $this->block_type->style ) ) { |
534 wp_enqueue_script( $view_script_handle ); |
271 wp_enqueue_style( $this->block_type->style ); |
535 } |
|
536 } |
|
537 |
|
538 if ( ! empty( $this->block_type->view_script_module_ids ) ) { |
|
539 foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) { |
|
540 wp_enqueue_script_module( $view_script_module_id ); |
|
541 } |
|
542 } |
|
543 |
|
544 if ( ( ! empty( $this->block_type->style_handles ) ) ) { |
|
545 foreach ( $this->block_type->style_handles as $style_handle ) { |
|
546 wp_enqueue_style( $style_handle ); |
|
547 } |
|
548 } |
|
549 |
|
550 if ( ( ! empty( $this->block_type->view_style_handles ) ) ) { |
|
551 foreach ( $this->block_type->view_style_handles as $view_style_handle ) { |
|
552 wp_enqueue_style( $view_style_handle ); |
|
553 } |
272 } |
554 } |
273 |
555 |
274 /** |
556 /** |
275 * Filters the content of a single block. |
557 * Filters the content of a single block. |
276 * |
558 * |
277 * @since 5.0.0 |
559 * @since 5.0.0 |
278 * @since 5.9.0 The `$instance` parameter was added. |
560 * @since 5.9.0 The `$instance` parameter was added. |
279 * |
561 * |
280 * @param string $block_content The block content about to be appended. |
562 * @param string $block_content The block content. |
281 * @param array $block The full block, including name and attributes. |
563 * @param array $block The full block, including name and attributes. |
282 * @param WP_Block $instance The block instance. |
564 * @param WP_Block $instance The block instance. |
283 */ |
565 */ |
284 $block_content = apply_filters( 'render_block', $block_content, $this->parsed_block, $this ); |
566 $block_content = apply_filters( 'render_block', $block_content, $this->parsed_block, $this ); |
285 |
567 |