[ 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      $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  }


Generated: Mon Jun 27 01:00:02 2022 Cross-referenced by PHPXref 0.7.1