[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> blocks.php (source)

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


Generated: Thu Sep 23 01:00:04 2021 Cross-referenced by PHPXref 0.7.1