[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Functions related to registering and parsing blocks. 4 * 5 * @package WordPress 6 * @subpackage Blocks 7 * @since 5.0.0 8 */ 9 10 /** 11 * Removes the block asset's path prefix if provided. 12 * 13 * @since 5.5.0 14 * 15 * @param string $asset_handle_or_path Asset handle or prefixed path. 16 * @return string Path without the prefix or the original value. 17 */ 18 function remove_block_asset_path_prefix( $asset_handle_or_path ) { 19 $path_prefix = 'file:'; 20 if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) { 21 return $asset_handle_or_path; 22 } 23 $path = substr( 24 $asset_handle_or_path, 25 strlen( $path_prefix ) 26 ); 27 if ( strpos( $path, './' ) === 0 ) { 28 $path = substr( $path, 2 ); 29 } 30 return $path; 31 } 32 33 /** 34 * Generates the name for an asset based on the name of the block 35 * and the field name provided. 36 * 37 * @since 5.5.0 38 * 39 * @param string $block_name Name of the block. 40 * @param string $field_name Name of the metadata field. 41 * @return string Generated asset name for the block's field. 42 */ 43 function generate_block_asset_handle( $block_name, $field_name ) { 44 if ( 0 === strpos( $block_name, 'core/' ) ) { 45 $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); 46 if ( 0 === strpos( $field_name, 'editor' ) ) { 47 $asset_handle .= '-editor'; 48 } 49 if ( 0 === strpos( $field_name, 'view' ) ) { 50 $asset_handle .= '-view'; 51 } 52 return $asset_handle; 53 } 54 55 $field_mappings = array( 56 'editorScript' => 'editor-script', 57 'script' => 'script', 58 'viewScript' => 'view-script', 59 'editorStyle' => 'editor-style', 60 'style' => 'style', 61 ); 62 return str_replace( '/', '-', $block_name ) . 63 '-' . $field_mappings[ $field_name ]; 64 } 65 66 /** 67 * Finds a script handle for the selected block metadata field. It detects 68 * when a path to file was provided and finds a corresponding asset file 69 * with details necessary to register the script under automatically 70 * generated handle name. It returns unprocessed script handle otherwise. 71 * 72 * @since 5.5.0 73 * 74 * @param array $metadata Block metadata. 75 * @param string $field_name Field name to pick from metadata. 76 * @return string|false Script handle provided directly or created through 77 * script's registration, or false on failure. 78 */ 79 function register_block_script_handle( $metadata, $field_name ) { 80 if ( empty( $metadata[ $field_name ] ) ) { 81 return false; 82 } 83 $script_handle = $metadata[ $field_name ]; 84 $script_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); 85 if ( $script_handle === $script_path ) { 86 return $script_handle; 87 } 88 89 $script_handle = generate_block_asset_handle( $metadata['name'], $field_name ); 90 $script_asset_path = wp_normalize_path( 91 realpath( 92 dirname( $metadata['file'] ) . '/' . 93 substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ) 94 ) 95 ); 96 if ( ! file_exists( $script_asset_path ) ) { 97 _doing_it_wrong( 98 __FUNCTION__, 99 sprintf( 100 /* translators: 1: Field name, 2: Block name. */ 101 __( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.' ), 102 $field_name, 103 $metadata['name'] 104 ), 105 '5.5.0' 106 ); 107 return false; 108 } 109 // Path needs to be normalized to work in Windows env. 110 $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); 111 $theme_path_norm = wp_normalize_path( get_theme_file_path() ); 112 $script_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $script_path ) ); 113 $is_core_block = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $wpinc_path_norm ); 114 $is_theme_block = 0 === strpos( $script_path_norm, $theme_path_norm ); 115 116 $script_uri = plugins_url( $script_path, $metadata['file'] ); 117 if ( $is_core_block ) { 118 $script_uri = includes_url( str_replace( $wpinc_path_norm, '', $script_path_norm ) ); 119 } elseif ( $is_theme_block ) { 120 $script_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $script_path_norm ) ); 121 } 122 123 $script_asset = require $script_asset_path; 124 $script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array(); 125 $result = wp_register_script( 126 $script_handle, 127 $script_uri, 128 $script_dependencies, 129 isset( $script_asset['version'] ) ? $script_asset['version'] : false 130 ); 131 if ( ! $result ) { 132 return false; 133 } 134 135 if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $script_dependencies, true ) ) { 136 wp_set_script_translations( $script_handle, $metadata['textdomain'] ); 137 } 138 139 return $script_handle; 140 } 141 142 /** 143 * Finds a style handle for the block metadata field. It detects when a path 144 * to file was provided and registers the style under automatically 145 * generated handle name. It returns unprocessed style handle otherwise. 146 * 147 * @since 5.5.0 148 * 149 * @param array $metadata Block metadata. 150 * @param string $field_name Field name to pick from metadata. 151 * @return string|false Style handle provided directly or created through 152 * style's registration, or false on failure. 153 */ 154 function register_block_style_handle( $metadata, $field_name ) { 155 if ( empty( $metadata[ $field_name ] ) ) { 156 return false; 157 } 158 $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); 159 $theme_path_norm = wp_normalize_path( get_theme_file_path() ); 160 $is_core_block = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $wpinc_path_norm ); 161 if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) { 162 return false; 163 } 164 165 // Check whether styles should have a ".min" suffix or not. 166 $suffix = SCRIPT_DEBUG ? '' : '.min'; 167 168 $style_handle = $metadata[ $field_name ]; 169 $style_path = remove_block_asset_path_prefix( $metadata[ $field_name ] ); 170 171 if ( $style_handle === $style_path && ! $is_core_block ) { 172 return $style_handle; 173 } 174 175 $style_uri = plugins_url( $style_path, $metadata['file'] ); 176 if ( $is_core_block ) { 177 $style_path = "style$suffix.css"; 178 $style_uri = includes_url( 'blocks/' . str_replace( 'core/', '', $metadata['name'] ) . "/style$suffix.css" ); 179 } 180 181 $style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) ); 182 $is_theme_block = 0 === strpos( $style_path_norm, $theme_path_norm ); 183 184 if ( $is_theme_block ) { 185 $style_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $style_path_norm ) ); 186 } 187 188 $style_handle = generate_block_asset_handle( $metadata['name'], $field_name ); 189 $block_dir = dirname( $metadata['file'] ); 190 $style_file = realpath( "$block_dir/$style_path" ); 191 $has_style_file = false !== $style_file; 192 $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; 193 $style_uri = $has_style_file ? $style_uri : false; 194 $result = wp_register_style( 195 $style_handle, 196 $style_uri, 197 array(), 198 $version 199 ); 200 if ( file_exists( str_replace( '.css', '-rtl.css', $style_file ) ) ) { 201 wp_style_add_data( $style_handle, 'rtl', 'replace' ); 202 } 203 if ( $has_style_file ) { 204 wp_style_add_data( $style_handle, 'path', $style_file ); 205 } 206 207 $rtl_file = str_replace( "$suffix.css", "-rtl$suffix.css", $style_file ); 208 if ( is_rtl() && file_exists( $rtl_file ) ) { 209 wp_style_add_data( $style_handle, 'path', $rtl_file ); 210 } 211 212 return $result ? $style_handle : false; 213 } 214 215 /** 216 * Gets i18n schema for block's metadata read from `block.json` file. 217 * 218 * @since 5.9.0 219 * 220 * @return object The schema for block's metadata. 221 */ 222 function get_block_metadata_i18n_schema() { 223 static $i18n_block_schema; 224 225 if ( ! isset( $i18n_block_schema ) ) { 226 $i18n_block_schema = wp_json_file_decode( __DIR__ . '/block-i18n.json' ); 227 } 228 229 return $i18n_block_schema; 230 } 231 232 /** 233 * Registers a block type from the metadata stored in the `block.json` file. 234 * 235 * @since 5.5.0 236 * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields. 237 * @since 5.9.0 Added support for `variations` and `viewScript` fields. 238 * 239 * @param string $file_or_folder Path to the JSON file with metadata definition for 240 * the block or path to the folder where the `block.json` file is located. 241 * If providing the path to a JSON file, the filename must end with `block.json`. 242 * @param array $args Optional. Array of block type arguments. Accepts any public property 243 * of `WP_Block_Type`. See WP_Block_Type::__construct() for information 244 * on accepted arguments. Default empty array. 245 * @return WP_Block_Type|false The registered block type on success, or false on failure. 246 */ 247 function register_block_type_from_metadata( $file_or_folder, $args = array() ) { 248 $filename = 'block.json'; 249 $metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ? 250 trailingslashit( $file_or_folder ) . $filename : 251 $file_or_folder; 252 if ( ! file_exists( $metadata_file ) ) { 253 return false; 254 } 255 256 $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) ); 257 if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) { 258 return false; 259 } 260 $metadata['file'] = wp_normalize_path( realpath( $metadata_file ) ); 261 262 /** 263 * Filters the metadata provided for registering a block type. 264 * 265 * @since 5.7.0 266 * 267 * @param array $metadata Metadata for registering a block type. 268 */ 269 $metadata = apply_filters( 'block_type_metadata', $metadata ); 270 271 // Add `style` and `editor_style` for core blocks if missing. 272 if ( ! empty( $metadata['name'] ) && 0 === strpos( $metadata['name'], 'core/' ) ) { 273 $block_name = str_replace( 'core/', '', $metadata['name'] ); 274 275 if ( ! isset( $metadata['style'] ) ) { 276 $metadata['style'] = "wp-block-$block_name"; 277 } 278 if ( ! isset( $metadata['editorStyle'] ) ) { 279 $metadata['editorStyle'] = "wp-block-{$block_name}-editor"; 280 } 281 } 282 283 $settings = array(); 284 $property_mappings = array( 285 'apiVersion' => 'api_version', 286 'title' => 'title', 287 'category' => 'category', 288 'parent' => 'parent', 289 'icon' => 'icon', 290 'description' => 'description', 291 'keywords' => 'keywords', 292 'attributes' => 'attributes', 293 'providesContext' => 'provides_context', 294 'usesContext' => 'uses_context', 295 'supports' => 'supports', 296 'styles' => 'styles', 297 'variations' => 'variations', 298 'example' => 'example', 299 ); 300 $textdomain = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null; 301 $i18n_schema = get_block_metadata_i18n_schema(); 302 303 foreach ( $property_mappings as $key => $mapped_key ) { 304 if ( isset( $metadata[ $key ] ) ) { 305 $settings[ $mapped_key ] = $metadata[ $key ]; 306 if ( $textdomain && isset( $i18n_schema->$key ) ) { 307 $settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain ); 308 } 309 } 310 } 311 312 if ( ! empty( $metadata['editorScript'] ) ) { 313 $settings['editor_script'] = register_block_script_handle( 314 $metadata, 315 'editorScript' 316 ); 317 } 318 319 if ( ! empty( $metadata['script'] ) ) { 320 $settings['script'] = register_block_script_handle( 321 $metadata, 322 'script' 323 ); 324 } 325 326 if ( ! empty( $metadata['viewScript'] ) ) { 327 $settings['view_script'] = register_block_script_handle( 328 $metadata, 329 'viewScript' 330 ); 331 } 332 333 if ( ! empty( $metadata['editorStyle'] ) ) { 334 $settings['editor_style'] = register_block_style_handle( 335 $metadata, 336 'editorStyle' 337 ); 338 } 339 340 if ( ! empty( $metadata['style'] ) ) { 341 $settings['style'] = register_block_style_handle( 342 $metadata, 343 'style' 344 ); 345 } 346 347 /** 348 * Filters the settings determined from the block type metadata. 349 * 350 * @since 5.7.0 351 * 352 * @param array $settings Array of determined settings for registering a block type. 353 * @param array $metadata Metadata provided for registering a block type. 354 */ 355 $settings = apply_filters( 356 'block_type_metadata_settings', 357 array_merge( 358 $settings, 359 $args 360 ), 361 $metadata 362 ); 363 364 return WP_Block_Type_Registry::get_instance()->register( 365 $metadata['name'], 366 $settings 367 ); 368 } 369 370 /** 371 * Registers a block type. The recommended way is to register a block type using 372 * the metadata stored in the `block.json` file. 373 * 374 * @since 5.0.0 375 * @since 5.8.0 First parameter now accepts a path to the `block.json` file. 376 * 377 * @param string|WP_Block_Type $block_type Block type name including namespace, or alternatively 378 * a path to the JSON file with metadata definition for the block, 379 * or a path to the folder where the `block.json` file is located, 380 * or a complete WP_Block_Type instance. 381 * In case a WP_Block_Type is provided, the $args parameter will be ignored. 382 * @param array $args Optional. Array of block type arguments. Accepts any public property 383 * of `WP_Block_Type`. See WP_Block_Type::__construct() for information 384 * on accepted arguments. Default empty array. 385 * 386 * @return WP_Block_Type|false The registered block type on success, or false on failure. 387 */ 388 function register_block_type( $block_type, $args = array() ) { 389 if ( is_string( $block_type ) && file_exists( $block_type ) ) { 390 return register_block_type_from_metadata( $block_type, $args ); 391 } 392 393 return WP_Block_Type_Registry::get_instance()->register( $block_type, $args ); 394 } 395 396 /** 397 * Unregisters a block type. 398 * 399 * @since 5.0.0 400 * 401 * @param string|WP_Block_Type $name Block type name including namespace, or alternatively 402 * a complete WP_Block_Type instance. 403 * @return WP_Block_Type|false The unregistered block type on success, or false on failure. 404 */ 405 function unregister_block_type( $name ) { 406 return WP_Block_Type_Registry::get_instance()->unregister( $name ); 407 } 408 409 /** 410 * Determines whether a post or content string has blocks. 411 * 412 * This test optimizes for performance rather than strict accuracy, detecting 413 * the pattern of a block but not validating its structure. For strict accuracy, 414 * you should use the block parser on post content. 415 * 416 * @since 5.0.0 417 * 418 * @see parse_blocks() 419 * 420 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. 421 * Defaults to global $post. 422 * @return bool Whether the post has blocks. 423 */ 424 function has_blocks( $post = null ) { 425 if ( ! is_string( $post ) ) { 426 $wp_post = get_post( $post ); 427 if ( $wp_post instanceof WP_Post ) { 428 $post = $wp_post->post_content; 429 } 430 } 431 432 return false !== strpos( (string) $post, '<!-- wp:' ); 433 } 434 435 /** 436 * Determines whether a $post or a string contains a specific block type. 437 * 438 * This test optimizes for performance rather than strict accuracy, detecting 439 * whether the block type exists but not validating its structure and not checking 440 * reusable blocks. For strict accuracy, you should use the block parser on post content. 441 * 442 * @since 5.0.0 443 * 444 * @see parse_blocks() 445 * 446 * @param string $block_name Full block type to look for. 447 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. 448 * Defaults to global $post. 449 * @return bool Whether the post content contains the specified block. 450 */ 451 function has_block( $block_name, $post = null ) { 452 if ( ! has_blocks( $post ) ) { 453 return false; 454 } 455 456 if ( ! is_string( $post ) ) { 457 $wp_post = get_post( $post ); 458 if ( $wp_post instanceof WP_Post ) { 459 $post = $wp_post->post_content; 460 } 461 } 462 463 /* 464 * Normalize block name to include namespace, if provided as non-namespaced. 465 * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by 466 * their serialized names. 467 */ 468 if ( false === strpos( $block_name, '/' ) ) { 469 $block_name = 'core/' . $block_name; 470 } 471 472 // Test for existence of block by its fully qualified name. 473 $has_block = false !== strpos( $post, '<!-- wp:' . $block_name . ' ' ); 474 475 if ( ! $has_block ) { 476 /* 477 * If the given block name would serialize to a different name, test for 478 * existence by the serialized form. 479 */ 480 $serialized_block_name = strip_core_block_namespace( $block_name ); 481 if ( $serialized_block_name !== $block_name ) { 482 $has_block = false !== strpos( $post, '<!-- wp:' . $serialized_block_name . ' ' ); 483 } 484 } 485 486 return $has_block; 487 } 488 489 /** 490 * Returns an array of the names of all registered dynamic block types. 491 * 492 * @since 5.0.0 493 * 494 * @return string[] Array of dynamic block names. 495 */ 496 function get_dynamic_block_names() { 497 $dynamic_block_names = array(); 498 499 $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); 500 foreach ( $block_types as $block_type ) { 501 if ( $block_type->is_dynamic() ) { 502 $dynamic_block_names[] = $block_type->name; 503 } 504 } 505 506 return $dynamic_block_names; 507 } 508 509 /** 510 * Given an array of attributes, returns a string in the serialized attributes 511 * format prepared for post content. 512 * 513 * The serialized result is a JSON-encoded string, with unicode escape sequence 514 * substitution for characters which might otherwise interfere with embedding 515 * the result in an HTML comment. 516 * 517 * This function must produce output that remains in sync with the output of 518 * the serializeAttributes JavaScript function in the block editor in order 519 * to ensure consistent operation between PHP and JavaScript. 520 * 521 * @since 5.3.1 522 * 523 * @param array $block_attributes Attributes object. 524 * @return string Serialized attributes. 525 */ 526 function serialize_block_attributes( $block_attributes ) { 527 $encoded_attributes = wp_json_encode( $block_attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); 528 $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes ); 529 $encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes ); 530 $encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes ); 531 $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes ); 532 // Regex: /\\"/ 533 $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes ); 534 535 return $encoded_attributes; 536 } 537 538 /** 539 * Returns the block name to use for serialization. This will remove the default 540 * "core/" namespace from a block name. 541 * 542 * @since 5.3.1 543 * 544 * @param string $block_name Original block name. 545 * @return string Block name to use for serialization. 546 */ 547 function strip_core_block_namespace( $block_name = null ) { 548 if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) { 549 return substr( $block_name, 5 ); 550 } 551 552 return $block_name; 553 } 554 555 /** 556 * Returns the content of a block, including comment delimiters. 557 * 558 * @since 5.3.1 559 * 560 * @param string|null $block_name Block name. Null if the block name is unknown, 561 * e.g. Classic blocks have their name set to null. 562 * @param array $block_attributes Block attributes. 563 * @param string $block_content Block save content. 564 * @return string Comment-delimited block content. 565 */ 566 function get_comment_delimited_block_content( $block_name, $block_attributes, $block_content ) { 567 if ( is_null( $block_name ) ) { 568 return $block_content; 569 } 570 571 $serialized_block_name = strip_core_block_namespace( $block_name ); 572 $serialized_attributes = empty( $block_attributes ) ? '' : serialize_block_attributes( $block_attributes ) . ' '; 573 574 if ( empty( $block_content ) ) { 575 return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes ); 576 } 577 578 return sprintf( 579 '<!-- wp:%s %s-->%s<!-- /wp:%s -->', 580 $serialized_block_name, 581 $serialized_attributes, 582 $block_content, 583 $serialized_block_name 584 ); 585 } 586 587 /** 588 * Returns the content of a block, including comment delimiters, serializing all 589 * attributes from the given parsed block. 590 * 591 * This should be used when preparing a block to be saved to post content. 592 * Prefer `render_block` when preparing a block for display. Unlike 593 * `render_block`, this does not evaluate a block's `render_callback`, and will 594 * instead preserve the markup as parsed. 595 * 596 * @since 5.3.1 597 * 598 * @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block. 599 * @return string String of rendered HTML. 600 */ 601 function serialize_block( $block ) { 602 $block_content = ''; 603 604 $index = 0; 605 foreach ( $block['innerContent'] as $chunk ) { 606 $block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ] ); 607 } 608 609 if ( ! is_array( $block['attrs'] ) ) { 610 $block['attrs'] = array(); 611 } 612 613 return get_comment_delimited_block_content( 614 $block['blockName'], 615 $block['attrs'], 616 $block_content 617 ); 618 } 619 620 /** 621 * Returns a joined string of the aggregate serialization of the given parsed 622 * blocks. 623 * 624 * @since 5.3.1 625 * 626 * @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block(). 627 * @return string String of rendered HTML. 628 */ 629 function serialize_blocks( $blocks ) { 630 return implode( '', array_map( 'serialize_block', $blocks ) ); 631 } 632 633 /** 634 * Filters and sanitizes block content to remove non-allowable HTML from 635 * parsed block attribute values. 636 * 637 * @since 5.3.1 638 * 639 * @param string $text Text that may contain block content. 640 * @param array[]|string $allowed_html An array of allowed HTML elements 641 * and attributes, or a context name 642 * such as 'post'. 643 * @param string[] $allowed_protocols Array of allowed URL protocols. 644 * @return string The filtered and sanitized content result. 645 */ 646 function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) { 647 $result = ''; 648 649 $blocks = parse_blocks( $text ); 650 foreach ( $blocks as $block ) { 651 $block = filter_block_kses( $block, $allowed_html, $allowed_protocols ); 652 $result .= serialize_block( $block ); 653 } 654 655 return $result; 656 } 657 658 /** 659 * Filters and sanitizes a parsed block to remove non-allowable HTML from block 660 * attribute values. 661 * 662 * @since 5.3.1 663 * 664 * @param WP_Block_Parser_Block $block The parsed block object. 665 * @param array[]|string $allowed_html An array of allowed HTML 666 * elements and attributes, or a 667 * context name such as 'post'. 668 * @param string[] $allowed_protocols Allowed URL protocols. 669 * @return array The filtered and sanitized block object result. 670 */ 671 function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) { 672 $block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols ); 673 674 if ( is_array( $block['innerBlocks'] ) ) { 675 foreach ( $block['innerBlocks'] as $i => $inner_block ) { 676 $block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols ); 677 } 678 } 679 680 return $block; 681 } 682 683 /** 684 * Filters and sanitizes a parsed block attribute value to remove non-allowable 685 * HTML. 686 * 687 * @since 5.3.1 688 * 689 * @param string[]|string $value The attribute value to filter. 690 * @param array[]|string $allowed_html An array of allowed HTML elements 691 * and attributes, or a context name 692 * such as 'post'. 693 * @param string[] $allowed_protocols Array of allowed URL protocols. 694 * @return string[]|string The filtered and sanitized result. 695 */ 696 function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) { 697 if ( is_array( $value ) ) { 698 foreach ( $value as $key => $inner_value ) { 699 $filtered_key = filter_block_kses_value( $key, $allowed_html, $allowed_protocols ); 700 $filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols ); 701 702 if ( $filtered_key !== $key ) { 703 unset( $value[ $key ] ); 704 } 705 706 $value[ $filtered_key ] = $filtered_value; 707 } 708 } elseif ( is_string( $value ) ) { 709 return wp_kses( $value, $allowed_html, $allowed_protocols ); 710 } 711 712 return $value; 713 } 714 715 /** 716 * Parses blocks out of a content string, and renders those appropriate for the excerpt. 717 * 718 * As the excerpt should be a small string of text relevant to the full post content, 719 * this function renders the blocks that are most likely to contain such text. 720 * 721 * @since 5.0.0 722 * 723 * @param string $content The content to parse. 724 * @return string The parsed and filtered content. 725 */ 726 function excerpt_remove_blocks( $content ) { 727 $allowed_inner_blocks = array( 728 // Classic blocks have their blockName set to null. 729 null, 730 'core/freeform', 731 'core/heading', 732 'core/html', 733 'core/list', 734 'core/media-text', 735 'core/paragraph', 736 'core/preformatted', 737 'core/pullquote', 738 'core/quote', 739 'core/table', 740 'core/verse', 741 ); 742 743 $allowed_wrapper_blocks = array( 744 'core/columns', 745 'core/column', 746 'core/group', 747 ); 748 749 /** 750 * Filters the list of blocks that can be used as wrapper blocks, allowing 751 * excerpts to be generated from the `innerBlocks` of these wrappers. 752 * 753 * @since 5.8.0 754 * 755 * @param string[] $allowed_wrapper_blocks The list of names of allowed wrapper blocks. 756 */ 757 $allowed_wrapper_blocks = apply_filters( 'excerpt_allowed_wrapper_blocks', $allowed_wrapper_blocks ); 758 759 $allowed_blocks = array_merge( $allowed_inner_blocks, $allowed_wrapper_blocks ); 760 761 /** 762 * Filters the list of blocks that can contribute to the excerpt. 763 * 764 * If a dynamic block is added to this list, it must not generate another 765 * excerpt, as this will cause an infinite loop to occur. 766 * 767 * @since 5.0.0 768 * 769 * @param string[] $allowed_blocks The list of names of allowed blocks. 770 */ 771 $allowed_blocks = apply_filters( 'excerpt_allowed_blocks', $allowed_blocks ); 772 $blocks = parse_blocks( $content ); 773 $output = ''; 774 775 foreach ( $blocks as $block ) { 776 if ( in_array( $block['blockName'], $allowed_blocks, true ) ) { 777 if ( ! empty( $block['innerBlocks'] ) ) { 778 if ( in_array( $block['blockName'], $allowed_wrapper_blocks, true ) ) { 779 $output .= _excerpt_render_inner_blocks( $block, $allowed_blocks ); 780 continue; 781 } 782 783 // Skip the block if it has disallowed or nested inner blocks. 784 foreach ( $block['innerBlocks'] as $inner_block ) { 785 if ( 786 ! in_array( $inner_block['blockName'], $allowed_inner_blocks, true ) || 787 ! empty( $inner_block['innerBlocks'] ) 788 ) { 789 continue 2; 790 } 791 } 792 } 793 794 $output .= render_block( $block ); 795 } 796 } 797 798 return $output; 799 } 800 801 /** 802 * Render inner blocks from the allowed wrapper blocks 803 * for generating an excerpt. 804 * 805 * @since 5.8.0 806 * @access private 807 * 808 * @param array $parsed_block The parsed block. 809 * @param array $allowed_blocks The list of allowed inner blocks. 810 * @return string The rendered inner blocks. 811 */ 812 function _excerpt_render_inner_blocks( $parsed_block, $allowed_blocks ) { 813 $output = ''; 814 815 foreach ( $parsed_block['innerBlocks'] as $inner_block ) { 816 if ( ! in_array( $inner_block['blockName'], $allowed_blocks, true ) ) { 817 continue; 818 } 819 820 if ( empty( $inner_block['innerBlocks'] ) ) { 821 $output .= render_block( $inner_block ); 822 } else { 823 $output .= _excerpt_render_inner_blocks( $inner_block, $allowed_blocks ); 824 } 825 } 826 827 return $output; 828 } 829 830 /** 831 * Renders a single block into a HTML string. 832 * 833 * @since 5.0.0 834 * 835 * @global WP_Post $post The post to edit. 836 * 837 * @param array $parsed_block A single parsed block object. 838 * @return string String of rendered HTML. 839 */ 840 function render_block( $parsed_block ) { 841 global $post; 842 $parent_block = null; 843 844 /** 845 * Allows render_block() to be short-circuited, by returning a non-null value. 846 * 847 * @since 5.1.0 848 * @since 5.9.0 The `$parent_block` parameter was added. 849 * 850 * @param string|null $pre_render The pre-rendered content. Default null. 851 * @param array $parsed_block The block being rendered. 852 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block. 853 */ 854 $pre_render = apply_filters( 'pre_render_block', null, $parsed_block, $parent_block ); 855 if ( ! is_null( $pre_render ) ) { 856 return $pre_render; 857 } 858 859 $source_block = $parsed_block; 860 861 /** 862 * Filters the block being rendered in render_block(), before it's processed. 863 * 864 * @since 5.1.0 865 * @since 5.9.0 The `$parent_block` parameter was added. 866 * 867 * @param array $parsed_block The block being rendered. 868 * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content. 869 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block. 870 */ 871 $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block, $parent_block ); 872 873 $context = array(); 874 875 if ( $post instanceof WP_Post ) { 876 $context['postId'] = $post->ID; 877 878 /* 879 * The `postType` context is largely unnecessary server-side, since the ID 880 * is usually sufficient on its own. That being said, since a block's 881 * manifest is expected to be shared between the server and the client, 882 * it should be included to consistently fulfill the expectation. 883 */ 884 $context['postType'] = $post->post_type; 885 } 886 887 /** 888 * Filters the default context provided to a rendered block. 889 * 890 * @since 5.5.0 891 * @since 5.9.0 The `$parent_block` parameter was added. 892 * 893 * @param array $context Default context. 894 * @param array $parsed_block Block being rendered, filtered by `render_block_data`. 895 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block. 896 */ 897 $context = apply_filters( 'render_block_context', $context, $parsed_block, $parent_block ); 898 899 $block = new WP_Block( $parsed_block, $context ); 900 901 return $block->render(); 902 } 903 904 /** 905 * Parses blocks out of a content string. 906 * 907 * @since 5.0.0 908 * 909 * @param string $content Post content. 910 * @return array[] Array of parsed block objects. 911 */ 912 function parse_blocks( $content ) { 913 /** 914 * Filter to allow plugins to replace the server-side block parser 915 * 916 * @since 5.0.0 917 * 918 * @param string $parser_class Name of block parser class. 919 */ 920 $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' ); 921 922 $parser = new $parser_class(); 923 return $parser->parse( $content ); 924 } 925 926 /** 927 * Parses dynamic blocks out of `post_content` and re-renders them. 928 * 929 * @since 5.0.0 930 * 931 * @param string $content Post content. 932 * @return string Updated post content. 933 */ 934 function do_blocks( $content ) { 935 $blocks = parse_blocks( $content ); 936 $output = ''; 937 938 foreach ( $blocks as $block ) { 939 $output .= render_block( $block ); 940 } 941 942 // If there are blocks in this content, we shouldn't run wpautop() on it later. 943 $priority = has_filter( 'the_content', 'wpautop' ); 944 if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) { 945 remove_filter( 'the_content', 'wpautop', $priority ); 946 add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 ); 947 } 948 949 return $output; 950 } 951 952 /** 953 * If do_blocks() needs to remove wpautop() from the `the_content` filter, this re-adds it afterwards, 954 * for subsequent `the_content` usage. 955 * 956 * @since 5.0.0 957 * @access private 958 * 959 * @param string $content The post content running through this filter. 960 * @return string The unmodified content. 961 */ 962 function _restore_wpautop_hook( $content ) { 963 $current_priority = has_filter( 'the_content', '_restore_wpautop_hook' ); 964 965 add_filter( 'the_content', 'wpautop', $current_priority - 1 ); 966 remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority ); 967 968 return $content; 969 } 970 971 /** 972 * Returns the current version of the block format that the content string is using. 973 * 974 * If the string doesn't contain blocks, it returns 0. 975 * 976 * @since 5.0.0 977 * 978 * @param string $content Content to test. 979 * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise. 980 */ 981 function block_version( $content ) { 982 return has_blocks( $content ) ? 1 : 0; 983 } 984 985 /** 986 * Registers a new block style. 987 * 988 * @since 5.3.0 989 * 990 * @param string $block_name Block type name including namespace. 991 * @param array $style_properties Array containing the properties of the style name, 992 * label, style (name of the stylesheet to be enqueued), 993 * inline_style (string containing the CSS to be added). 994 * @return bool True if the block style was registered with success and false otherwise. 995 */ 996 function register_block_style( $block_name, $style_properties ) { 997 return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties ); 998 } 999 1000 /** 1001 * Unregisters a block style. 1002 * 1003 * @since 5.3.0 1004 * 1005 * @param string $block_name Block type name including namespace. 1006 * @param string $block_style_name Block style name. 1007 * @return bool True if the block style was unregistered with success and false otherwise. 1008 */ 1009 function unregister_block_style( $block_name, $block_style_name ) { 1010 return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name ); 1011 } 1012 1013 /** 1014 * Checks whether the current block type supports the feature requested. 1015 * 1016 * @since 5.8.0 1017 * 1018 * @param WP_Block_Type $block_type Block type to check for support. 1019 * @param string $feature Name of the feature to check support for. 1020 * @param mixed $default Optional. Fallback value for feature support. Default false. 1021 * @return bool Whether the feature is supported. 1022 */ 1023 function block_has_support( $block_type, $feature, $default = false ) { 1024 $block_support = $default; 1025 if ( $block_type && property_exists( $block_type, 'supports' ) ) { 1026 $block_support = _wp_array_get( $block_type->supports, $feature, $default ); 1027 } 1028 1029 return true === $block_support || is_array( $block_support ); 1030 } 1031 1032 /** 1033 * Converts typography keys declared under `supports.*` to `supports.typography.*`. 1034 * 1035 * Displays a `_doing_it_wrong()` notice when a block using the older format is detected. 1036 * 1037 * @since 5.8.0 1038 * 1039 * @param array $metadata Metadata for registering a block type. 1040 * @return array Filtered metadata for registering a block type. 1041 */ 1042 function wp_migrate_old_typography_shape( $metadata ) { 1043 if ( ! isset( $metadata['supports'] ) ) { 1044 return $metadata; 1045 } 1046 1047 $typography_keys = array( 1048 '__experimentalFontFamily', 1049 '__experimentalFontStyle', 1050 '__experimentalFontWeight', 1051 '__experimentalLetterSpacing', 1052 '__experimentalTextDecoration', 1053 '__experimentalTextTransform', 1054 'fontSize', 1055 'lineHeight', 1056 ); 1057 1058 foreach ( $typography_keys as $typography_key ) { 1059 $support_for_key = _wp_array_get( $metadata['supports'], array( $typography_key ), null ); 1060 1061 if ( null !== $support_for_key ) { 1062 _doing_it_wrong( 1063 'register_block_type_from_metadata()', 1064 sprintf( 1065 /* translators: 1: Block type, 2: Typography supports key, e.g: fontSize, lineHeight, etc. 3: block.json, 4: Old metadata key, 5: New metadata key. */ 1066 __( 'Block "%1$s" is declaring %2$s support in %3$s file under %4$s. %2$s support is now declared under %5$s.' ), 1067 $metadata['name'], 1068 "<code>$typography_key</code>", 1069 '<code>block.json</code>', 1070 "<code>supports.$typography_key</code>", 1071 "<code>supports.typography.$typography_key</code>" 1072 ), 1073 '5.8.0' 1074 ); 1075 1076 _wp_array_set( $metadata['supports'], array( 'typography', $typography_key ), $support_for_key ); 1077 unset( $metadata['supports'][ $typography_key ] ); 1078 } 1079 } 1080 1081 return $metadata; 1082 } 1083 1084 /** 1085 * Helper function that constructs a WP_Query args array from 1086 * a `Query` block properties. 1087 * 1088 * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks. 1089 * 1090 * @since 5.8.0 1091 * 1092 * @param WP_Block $block Block instance. 1093 * @param int $page Current query's page. 1094 * 1095 * @return array Returns the constructed WP_Query arguments. 1096 */ 1097 function build_query_vars_from_query_block( $block, $page ) { 1098 $query = array( 1099 'post_type' => 'post', 1100 'order' => 'DESC', 1101 'orderby' => 'date', 1102 'post__not_in' => array(), 1103 ); 1104 1105 if ( isset( $block->context['query'] ) ) { 1106 if ( ! empty( $block->context['query']['postType'] ) ) { 1107 $post_type_param = $block->context['query']['postType']; 1108 if ( is_post_type_viewable( $post_type_param ) ) { 1109 $query['post_type'] = $post_type_param; 1110 } 1111 } 1112 if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) { 1113 $sticky = get_option( 'sticky_posts' ); 1114 if ( 'only' === $block->context['query']['sticky'] ) { 1115 $query['post__in'] = $sticky; 1116 } else { 1117 $query['post__not_in'] = array_merge( $query['post__not_in'], $sticky ); 1118 } 1119 } 1120 if ( ! empty( $block->context['query']['exclude'] ) ) { 1121 $excluded_post_ids = array_map( 'intval', $block->context['query']['exclude'] ); 1122 $excluded_post_ids = array_filter( $excluded_post_ids ); 1123 $query['post__not_in'] = array_merge( $query['post__not_in'], $excluded_post_ids ); 1124 } 1125 if ( 1126 isset( $block->context['query']['perPage'] ) && 1127 is_numeric( $block->context['query']['perPage'] ) 1128 ) { 1129 $per_page = absint( $block->context['query']['perPage'] ); 1130 $offset = 0; 1131 1132 if ( 1133 isset( $block->context['query']['offset'] ) && 1134 is_numeric( $block->context['query']['offset'] ) 1135 ) { 1136 $offset = absint( $block->context['query']['offset'] ); 1137 } 1138 1139 $query['offset'] = ( $per_page * ( $page - 1 ) ) + $offset; 1140 $query['posts_per_page'] = $per_page; 1141 } 1142 // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility. 1143 if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) { 1144 $tax_query = array(); 1145 if ( ! empty( $block->context['query']['categoryIds'] ) ) { 1146 $tax_query[] = array( 1147 'taxonomy' => 'category', 1148 'terms' => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ), 1149 'include_children' => false, 1150 ); 1151 } 1152 if ( ! empty( $block->context['query']['tagIds'] ) ) { 1153 $tax_query[] = array( 1154 'taxonomy' => 'post_tag', 1155 'terms' => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ), 1156 'include_children' => false, 1157 ); 1158 } 1159 $query['tax_query'] = $tax_query; 1160 } 1161 if ( ! empty( $block->context['query']['taxQuery'] ) ) { 1162 $query['tax_query'] = array(); 1163 foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) { 1164 if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) { 1165 $query['tax_query'][] = array( 1166 'taxonomy' => $taxonomy, 1167 'terms' => array_filter( array_map( 'intval', $terms ) ), 1168 'include_children' => false, 1169 ); 1170 } 1171 } 1172 } 1173 if ( 1174 isset( $block->context['query']['order'] ) && 1175 in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true ) 1176 ) { 1177 $query['order'] = strtoupper( $block->context['query']['order'] ); 1178 } 1179 if ( isset( $block->context['query']['orderBy'] ) ) { 1180 $query['orderby'] = $block->context['query']['orderBy']; 1181 } 1182 if ( 1183 isset( $block->context['query']['author'] ) && 1184 (int) $block->context['query']['author'] > 0 1185 ) { 1186 $query['author'] = (int) $block->context['query']['author']; 1187 } 1188 if ( ! empty( $block->context['query']['search'] ) ) { 1189 $query['s'] = $block->context['query']['search']; 1190 } 1191 } 1192 return $query; 1193 } 1194 1195 /** 1196 * Helper function that returns the proper pagination arrow HTML for 1197 * `QueryPaginationNext` and `QueryPaginationPrevious` blocks based 1198 * on the provided `paginationArrow` from `QueryPagination` context. 1199 * 1200 * It's used in QueryPaginationNext and QueryPaginationPrevious blocks. 1201 * 1202 * @since 5.9.0 1203 * 1204 * @param WP_Block $block Block instance. 1205 * @param boolean $is_next Flag for handling `next/previous` blocks. 1206 * 1207 * @return string|null The pagination arrow HTML or null if there is none. 1208 */ 1209 function get_query_pagination_arrow( $block, $is_next ) { 1210 $arrow_map = array( 1211 'none' => '', 1212 'arrow' => array( 1213 'next' => '→', 1214 'previous' => '←', 1215 ), 1216 'chevron' => array( 1217 'next' => '»', 1218 'previous' => '«', 1219 ), 1220 ); 1221 if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists( $block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map[ $block->context['paginationArrow'] ] ) ) { 1222 $pagination_type = $is_next ? 'next' : 'previous'; 1223 $arrow_attribute = $block->context['paginationArrow']; 1224 $arrow = $arrow_map[ $block->context['paginationArrow'] ][ $pagination_type ]; 1225 $arrow_classes = "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute"; 1226 return "<span class='$arrow_classes'>$arrow</span>"; 1227 } 1228 return null; 1229 } 1230 1231 /** 1232 * Allows multiple block styles. 1233 * 1234 * @since 5.9.0 1235 * 1236 * @param array $metadata Metadata for registering a block type. 1237 * @return array Metadata for registering a block type. 1238 */ 1239 function _wp_multiple_block_styles( $metadata ) { 1240 foreach ( array( 'style', 'editorStyle' ) as $key ) { 1241 if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) { 1242 $default_style = array_shift( $metadata[ $key ] ); 1243 foreach ( $metadata[ $key ] as $handle ) { 1244 $args = array( 'handle' => $handle ); 1245 if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) { 1246 $style_path = remove_block_asset_path_prefix( $handle ); 1247 $theme_path_norm = wp_normalize_path( get_theme_file_path() ); 1248 $style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) ); 1249 $is_theme_block = isset( $metadata['file'] ) && 0 === strpos( $metadata['file'], $theme_path_norm ); 1250 1251 $style_uri = plugins_url( $style_path, $metadata['file'] ); 1252 1253 if ( $is_theme_block ) { 1254 $style_uri = get_theme_file_uri( str_replace( $theme_path_norm, '', $style_path_norm ) ); 1255 } 1256 1257 $args = array( 1258 'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ), 1259 'src' => $style_uri, 1260 ); 1261 } 1262 1263 wp_enqueue_block_style( $metadata['name'], $args ); 1264 } 1265 1266 // Only return the 1st item in the array. 1267 $metadata[ $key ] = $default_style; 1268 } 1269 } 1270 return $metadata; 1271 } 1272 add_filter( 'block_type_metadata', '_wp_multiple_block_styles' ); 1273 1274 /** 1275 * Helper function that constructs a comment query vars array from the passed 1276 * block properties. 1277 * 1278 * It's used with the Comment Query Loop inner blocks. 1279 * 1280 * @since 6.0.0 1281 * 1282 * @param WP_Block $block Block instance. 1283 * 1284 * @return array Returns the comment query parameters to use with the 1285 * WP_Comment_Query constructor. 1286 */ 1287 function build_comment_query_vars_from_block( $block ) { 1288 1289 $comment_args = array( 1290 'orderby' => 'comment_date_gmt', 1291 'order' => 'ASC', 1292 'status' => 'approve', 1293 'no_found_rows' => false, 1294 ); 1295 1296 if ( is_user_logged_in() ) { 1297 $comment_args['include_unapproved'] = array( get_current_user_id() ); 1298 } else { 1299 $unapproved_email = wp_get_unapproved_comment_author_email(); 1300 1301 if ( $unapproved_email ) { 1302 $comment_args['include_unapproved'] = array( $unapproved_email ); 1303 } 1304 } 1305 1306 if ( ! empty( $block->context['postId'] ) ) { 1307 $comment_args['post_id'] = (int) $block->context['postId']; 1308 } 1309 1310 if ( get_option( 'thread_comments' ) ) { 1311 $comment_args['hierarchical'] = 'threaded'; 1312 } else { 1313 $comment_args['hierarchical'] = false; 1314 } 1315 1316 if ( get_option( 'page_comments' ) === '1' || get_option( 'page_comments' ) === true ) { 1317 $per_page = get_option( 'comments_per_page' ); 1318 $default_page = get_option( 'default_comments_page' ); 1319 if ( $per_page > 0 ) { 1320 $comment_args['number'] = $per_page; 1321 1322 $page = (int) get_query_var( 'cpage' ); 1323 if ( $page ) { 1324 $comment_args['paged'] = $page; 1325 } elseif ( 'oldest' === $default_page ) { 1326 $comment_args['paged'] = 1; 1327 } elseif ( 'newest' === $default_page ) { 1328 $max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages; 1329 if ( 0 !== $max_num_pages ) { 1330 $comment_args['paged'] = $max_num_pages; 1331 } 1332 } 1333 // Set the `cpage` query var to ensure the previous and next pagination links are correct 1334 // when inheriting the Discussion Settings. 1335 if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) { 1336 set_query_var( 'cpage', $comment_args['paged'] ); 1337 } 1338 } 1339 } 1340 1341 return $comment_args; 1342 } 1343 1344 /** 1345 * Helper function that returns the proper pagination arrow HTML for 1346 * `CommentsPaginationNext` and `CommentsPaginationPrevious` blocks based on the 1347 * provided `paginationArrow` from `CommentsPagination` context. 1348 * 1349 * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks. 1350 * 1351 * @since 6.0.0 1352 * 1353 * @param WP_Block $block Block instance. 1354 * @param string $pagination_type Type of the arrow we will be rendering. 1355 * Default 'next'. Accepts 'next' or 'previous'. 1356 * 1357 * @return string|null The pagination arrow HTML or null if there is none. 1358 */ 1359 function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) { 1360 $arrow_map = array( 1361 'none' => '', 1362 'arrow' => array( 1363 'next' => '→', 1364 'previous' => '←', 1365 ), 1366 'chevron' => array( 1367 'next' => '»', 1368 'previous' => '«', 1369 ), 1370 ); 1371 if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) { 1372 $arrow_attribute = $block->context['comments/paginationArrow']; 1373 $arrow = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ]; 1374 $arrow_classes = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute"; 1375 return "<span class='$arrow_classes'>$arrow</span>"; 1376 } 1377 return null; 1378 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |