[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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   * Registers a block type.
  12   *
  13   * @since 5.0.0
  14   *
  15   * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
  16   *                                   complete WP_Block_Type instance. In case a WP_Block_Type
  17   *                                   is provided, the $args parameter will be ignored.
  18   * @param array                $args {
  19   *     Optional. Array of block type arguments. Any arguments may be defined, however the
  20   *     ones described below are supported by default. Default empty array.
  21   *
  22   *     @type callable $render_callback Callback used to render blocks of this block type.
  23   * }
  24   * @return WP_Block_Type|false The registered block type on success, or false on failure.
  25   */
  26  function register_block_type( $name, $args = array() ) {
  27      return WP_Block_Type_Registry::get_instance()->register( $name, $args );
  28  }
  29  
  30  /**
  31   * Unregisters a block type.
  32   *
  33   * @since 5.0.0
  34   *
  35   * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
  36   *                                   complete WP_Block_Type instance.
  37   * @return WP_Block_Type|false The unregistered block type on success, or false on failure.
  38   */
  39  function unregister_block_type( $name ) {
  40      return WP_Block_Type_Registry::get_instance()->unregister( $name );
  41  }
  42  
  43  /**
  44   * Determine whether a post or content string has blocks.
  45   *
  46   * This test optimizes for performance rather than strict accuracy, detecting
  47   * the pattern of a block but not validating its structure. For strict accuracy,
  48   * you should use the block parser on post content.
  49   *
  50   * @since 5.0.0
  51   * @see parse_blocks()
  52   *
  53   * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
  54   * @return bool Whether the post has blocks.
  55   */
  56  function has_blocks( $post = null ) {
  57      if ( ! is_string( $post ) ) {
  58          $wp_post = get_post( $post );
  59          if ( $wp_post instanceof WP_Post ) {
  60              $post = $wp_post->post_content;
  61          }
  62      }
  63  
  64      return false !== strpos( (string) $post, '<!-- wp:' );
  65  }
  66  
  67  /**
  68   * Determine whether a $post or a string contains a specific block type.
  69   *
  70   * This test optimizes for performance rather than strict accuracy, detecting
  71   * the block type exists but not validating its structure. For strict accuracy,
  72   * you should use the block parser on post content.
  73   *
  74   * @since 5.0.0
  75   * @see parse_blocks()
  76   *
  77   * @param string                  $block_name Full Block type to look for.
  78   * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
  79   * @return bool Whether the post content contains the specified block.
  80   */
  81  function has_block( $block_name, $post = null ) {
  82      if ( ! has_blocks( $post ) ) {
  83          return false;
  84      }
  85  
  86      if ( ! is_string( $post ) ) {
  87          $wp_post = get_post( $post );
  88          if ( $wp_post instanceof WP_Post ) {
  89              $post = $wp_post->post_content;
  90          }
  91      }
  92  
  93      /*
  94       * Normalize block name to include namespace, if provided as non-namespaced.
  95       * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by
  96       * their serialized names.
  97       */
  98      if ( false === strpos( $block_name, '/' ) ) {
  99          $block_name = 'core/' . $block_name;
 100      }
 101  
 102      // Test for existence of block by its fully qualified name.
 103      $has_block = false !== strpos( $post, '<!-- wp:' . $block_name . ' ' );
 104  
 105      if ( ! $has_block ) {
 106          /*
 107           * If the given block name would serialize to a different name, test for
 108           * existence by the serialized form.
 109           */
 110          $serialized_block_name = strip_core_block_namespace( $block_name );
 111          if ( $serialized_block_name !== $block_name ) {
 112              $has_block = false !== strpos( $post, '<!-- wp:' . $serialized_block_name . ' ' );
 113          }
 114      }
 115  
 116      return $has_block;
 117  }
 118  
 119  /**
 120   * Returns an array of the names of all registered dynamic block types.
 121   *
 122   * @since 5.0.0
 123   *
 124   * @return string[] Array of dynamic block names.
 125   */
 126  function get_dynamic_block_names() {
 127      $dynamic_block_names = array();
 128  
 129      $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
 130      foreach ( $block_types as $block_type ) {
 131          if ( $block_type->is_dynamic() ) {
 132              $dynamic_block_names[] = $block_type->name;
 133          }
 134      }
 135  
 136      return $dynamic_block_names;
 137  }
 138  
 139  /**
 140   * Given an array of attributes, returns a string in the serialized attributes
 141   * format prepared for post content.
 142   *
 143   * The serialized result is a JSON-encoded string, with unicode escape sequence
 144   * substitution for characters which might otherwise interfere with embedding
 145   * the result in an HTML comment.
 146   *
 147   * @since 5.3.1
 148   *
 149   * @param array $attributes Attributes object.
 150   * @return string Serialized attributes.
 151   */
 152  function serialize_block_attributes( $block_attributes ) {
 153      $encoded_attributes = json_encode( $block_attributes );
 154      $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes );
 155      $encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes );
 156      $encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes );
 157      $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes );
 158      // Regex: /\\"/
 159      $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes );
 160  
 161      return $encoded_attributes;
 162  }
 163  
 164  /**
 165   * Returns the block name to use for serialization. This will remove the default
 166   * "core/" namespace from a block name.
 167   *
 168   * @since 5.3.1
 169   *
 170   * @param string $block_name Original block name.
 171   * @return string Block name to use for serialization.
 172   */
 173  function strip_core_block_namespace( $block_name = null ) {
 174      if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) {
 175          return substr( $block_name, 5 );
 176      }
 177  
 178      return $block_name;
 179  }
 180  
 181  /**
 182   * Returns the content of a block, including comment delimiters.
 183   *
 184   * @since 5.3.1
 185   *
 186   * @param string $block_name Block name.
 187   * @param array  $attributes Block attributes.
 188   * @param string $content    Block save content.
 189   * @return string Comment-delimited block content.
 190   */
 191  function get_comment_delimited_block_content( $block_name = null, $block_attributes, $block_content ) {
 192      if ( is_null( $block_name ) ) {
 193          return $block_content;
 194      }
 195  
 196      $serialized_block_name = strip_core_block_namespace( $block_name );
 197      $serialized_attributes = empty( $block_attributes ) ? '' : serialize_block_attributes( $block_attributes ) . ' ';
 198  
 199      if ( empty( $block_content ) ) {
 200          return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes );
 201      }
 202  
 203      return sprintf(
 204          '<!-- wp:%s %s-->%s<!-- /wp:%s -->',
 205          $serialized_block_name,
 206          $serialized_attributes,
 207          $block_content,
 208          $serialized_block_name
 209      );
 210  }
 211  
 212  /**
 213   * Returns the content of a block, including comment delimiters, serializing all
 214   * attributes from the given parsed block.
 215   *
 216   * This should be used when preparing a block to be saved to post content.
 217   * Prefer `render_block` when preparing a block for display. Unlike
 218   * `render_block`, this does not evaluate a block's `render_callback`, and will
 219   * instead preserve the markup as parsed.
 220   *
 221   * @since 5.3.1
 222   *
 223   * @param WP_Block_Parser_Block $block A single parsed block object.
 224   * @return string String of rendered HTML.
 225   */
 226  function serialize_block( $block ) {
 227      $block_content = '';
 228  
 229      $index = 0;
 230      foreach ( $block['innerContent'] as $chunk ) {
 231          $block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ] );
 232      }
 233  
 234      if ( ! is_array( $block['attrs'] ) ) {
 235          $block['attrs'] = array();
 236      }
 237  
 238      return get_comment_delimited_block_content(
 239          $block['blockName'],
 240          $block['attrs'],
 241          $block_content
 242      );
 243  }
 244  
 245  /**
 246   * Returns a joined string of the aggregate serialization of the given parsed
 247   * blocks.
 248   *
 249   * @since 5.3.1
 250   *
 251   * @param WP_Block_Parser_Block[] $blocks Parsed block objects.
 252   * @return string String of rendered HTML.
 253   */
 254  function serialize_blocks( $blocks ) {
 255      return implode( '', array_map( 'serialize_block', $blocks ) );
 256  }
 257  
 258  /**
 259   * Filters and sanitizes block content to remove non-allowable HTML from
 260   * parsed block attribute values.
 261   *
 262   * @since 5.3.1
 263   *
 264   * @param string         $text              Text that may contain block content.
 265   * @param array[]|string $allowed_html      An array of allowed HTML elements
 266   *                                          and attributes, or a context name
 267   *                                          such as 'post'.
 268   * @param string[]       $allowed_protocols Array of allowed URL protocols.
 269   * @return string The filtered and sanitized content result.
 270   */
 271  function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
 272      $result = '';
 273  
 274      $blocks = parse_blocks( $text );
 275      foreach ( $blocks as $block ) {
 276          $block   = filter_block_kses( $block, $allowed_html, $allowed_protocols );
 277          $result .= serialize_block( $block );
 278      }
 279  
 280      return $result;
 281  }
 282  
 283  /**
 284   * Filters and sanitizes a parsed block to remove non-allowable HTML from block
 285   * attribute values.
 286   *
 287   * @since 5.3.1
 288   *
 289   * @param WP_Block_Parser_Block $block             The parsed block object.
 290   * @param array[]|string        $allowed_html      An array of allowed HTML
 291   *                                                 elements and attributes, or a
 292   *                                                 context name such as 'post'.
 293   * @param string[]              $allowed_protocols Allowed URL protocols.
 294   * @return array The filtered and sanitized block object result.
 295   */
 296  function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
 297      $block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols );
 298  
 299      if ( is_array( $block['innerBlocks'] ) ) {
 300          foreach ( $block['innerBlocks'] as $i => $inner_block ) {
 301              $block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols );
 302          }
 303      }
 304  
 305      return $block;
 306  }
 307  
 308  /**
 309   * Filters and sanitizes a parsed block attribute value to remove non-allowable
 310   * HTML.
 311   *
 312   * @since 5.3.1
 313   *
 314   * @param string[]|string $value             The attribute value to filter.
 315   * @param array[]|string  $allowed_html      An array of allowed HTML elements
 316   *                                           and attributes, or a context name
 317   *                                           such as 'post'.
 318   * @param string[]        $allowed_protocols Array of allowed URL protocols.
 319   * @return string[]|string The filtered and sanitized result.
 320   */
 321  function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) {
 322      if ( is_array( $value ) ) {
 323          foreach ( $value as $key => $inner_value ) {
 324              $filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols );
 325              $filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols );
 326  
 327              if ( $filtered_key !== $key ) {
 328                  unset( $value[ $key ] );
 329              }
 330  
 331              $value[ $filtered_key ] = $filtered_value;
 332          }
 333      } elseif ( is_string( $value ) ) {
 334          return wp_kses( $value, $allowed_html, $allowed_protocols );
 335      }
 336  
 337      return $value;
 338  }
 339  
 340  /**
 341   * Parses blocks out of a content string, and renders those appropriate for the excerpt.
 342   *
 343   * As the excerpt should be a small string of text relevant to the full post content,
 344   * this function renders the blocks that are most likely to contain such text.
 345   *
 346   * @since 5.0.0
 347   *
 348   * @param string $content The content to parse.
 349   * @return string The parsed and filtered content.
 350   */
 351  function excerpt_remove_blocks( $content ) {
 352      $allowed_inner_blocks = array(
 353          // Classic blocks have their blockName set to null.
 354          null,
 355          'core/freeform',
 356          'core/heading',
 357          'core/html',
 358          'core/list',
 359          'core/media-text',
 360          'core/paragraph',
 361          'core/preformatted',
 362          'core/pullquote',
 363          'core/quote',
 364          'core/table',
 365          'core/verse',
 366      );
 367  
 368      $allowed_blocks = array_merge( $allowed_inner_blocks, array( 'core/columns' ) );
 369  
 370      /**
 371       * Filters the list of blocks that can contribute to the excerpt.
 372       *
 373       * If a dynamic block is added to this list, it must not generate another
 374       * excerpt, as this will cause an infinite loop to occur.
 375       *
 376       * @since 5.0.0
 377       *
 378       * @param array $allowed_blocks The list of allowed blocks.
 379       */
 380      $allowed_blocks = apply_filters( 'excerpt_allowed_blocks', $allowed_blocks );
 381      $blocks         = parse_blocks( $content );
 382      $output         = '';
 383  
 384      foreach ( $blocks as $block ) {
 385          if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
 386              if ( ! empty( $block['innerBlocks'] ) ) {
 387                  if ( 'core/columns' === $block['blockName'] ) {
 388                      $output .= _excerpt_render_inner_columns_blocks( $block, $allowed_inner_blocks );
 389                      continue;
 390                  }
 391  
 392                  // Skip the block if it has disallowed or nested inner blocks.
 393                  foreach ( $block['innerBlocks'] as $inner_block ) {
 394                      if (
 395                          ! in_array( $inner_block['blockName'], $allowed_inner_blocks, true ) ||
 396                          ! empty( $inner_block['innerBlocks'] )
 397                      ) {
 398                          continue 2;
 399                      }
 400                  }
 401              }
 402  
 403              $output .= render_block( $block );
 404          }
 405      }
 406  
 407      return $output;
 408  }
 409  
 410  /**
 411   * Render inner blocks from the `core/columns` block for generating an excerpt.
 412   *
 413   * @since 5.2.0
 414   * @access private
 415   *
 416   * @param array $columns        The parsed columns block.
 417   * @param array $allowed_blocks The list of allowed inner blocks.
 418   * @return string The rendered inner blocks.
 419   */
 420  function _excerpt_render_inner_columns_blocks( $columns, $allowed_blocks ) {
 421      $output = '';
 422  
 423      foreach ( $columns['innerBlocks'] as $column ) {
 424          foreach ( $column['innerBlocks'] as $inner_block ) {
 425              if ( in_array( $inner_block['blockName'], $allowed_blocks, true ) && empty( $inner_block['innerBlocks'] ) ) {
 426                  $output .= render_block( $inner_block );
 427              }
 428          }
 429      }
 430  
 431      return $output;
 432  }
 433  
 434  /**
 435   * Renders a single block into a HTML string.
 436   *
 437   * @since 5.0.0
 438   *
 439   * @global WP_Post $post The post to edit.
 440   *
 441   * @param array $block A single parsed block object.
 442   * @return string String of rendered HTML.
 443   */
 444  function render_block( $block ) {
 445      global $post;
 446  
 447      /**
 448       * Allows render_block() to be shortcircuited, by returning a non-null value.
 449       *
 450       * @since 5.1.0
 451       *
 452       * @param string|null $pre_render The pre-rendered content. Default null.
 453       * @param array       $block      The block being rendered.
 454       */
 455      $pre_render = apply_filters( 'pre_render_block', null, $block );
 456      if ( ! is_null( $pre_render ) ) {
 457          return $pre_render;
 458      }
 459  
 460      $source_block = $block;
 461  
 462      /**
 463       * Filters the block being rendered in render_block(), before it's processed.
 464       *
 465       * @since 5.1.0
 466       *
 467       * @param array $block        The block being rendered.
 468       * @param array $source_block An un-modified copy of $block, as it appeared in the source content.
 469       */
 470      $block = apply_filters( 'render_block_data', $block, $source_block );
 471  
 472      $block_type    = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
 473      $is_dynamic    = $block['blockName'] && null !== $block_type && $block_type->is_dynamic();
 474      $block_content = '';
 475      $index         = 0;
 476  
 477      foreach ( $block['innerContent'] as $chunk ) {
 478          $block_content .= is_string( $chunk ) ? $chunk : render_block( $block['innerBlocks'][ $index++ ] );
 479      }
 480  
 481      if ( ! is_array( $block['attrs'] ) ) {
 482          $block['attrs'] = array();
 483      }
 484  
 485      if ( $is_dynamic ) {
 486          $global_post   = $post;
 487          $block_content = $block_type->render( $block['attrs'], $block_content );
 488          $post          = $global_post;
 489      }
 490  
 491      /**
 492       * Filters the content of a single block.
 493       *
 494       * @since 5.0.0
 495       *
 496       * @param string $block_content The block content about to be appended.
 497       * @param array  $block         The full block, including name and attributes.
 498       */
 499      return apply_filters( 'render_block', $block_content, $block );
 500  }
 501  
 502  /**
 503   * Parses blocks out of a content string.
 504   *
 505   * @since 5.0.0
 506   *
 507   * @param string $content Post content.
 508   * @return array[] Array of parsed block objects.
 509   */
 510  function parse_blocks( $content ) {
 511      /**
 512       * Filter to allow plugins to replace the server-side block parser
 513       *
 514       * @since 5.0.0
 515       *
 516       * @param string $parser_class Name of block parser class.
 517       */
 518      $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' );
 519  
 520      $parser = new $parser_class();
 521      return $parser->parse( $content );
 522  }
 523  
 524  /**
 525   * Parses dynamic blocks out of `post_content` and re-renders them.
 526   *
 527   * @since 5.0.0
 528   *
 529   * @param string $content Post content.
 530   * @return string Updated post content.
 531   */
 532  function do_blocks( $content ) {
 533      $blocks = parse_blocks( $content );
 534      $output = '';
 535  
 536      foreach ( $blocks as $block ) {
 537          $output .= render_block( $block );
 538      }
 539  
 540      // If there are blocks in this content, we shouldn't run wpautop() on it later.
 541      $priority = has_filter( 'the_content', 'wpautop' );
 542      if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
 543          remove_filter( 'the_content', 'wpautop', $priority );
 544          add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
 545      }
 546  
 547      return $output;
 548  }
 549  
 550  /**
 551   * If do_blocks() needs to remove wpautop() from the `the_content` filter, this re-adds it afterwards,
 552   * for subsequent `the_content` usage.
 553   *
 554   * @access private
 555   *
 556   * @since 5.0.0
 557   *
 558   * @param string $content The post content running through this filter.
 559   * @return string The unmodified content.
 560   */
 561  function _restore_wpautop_hook( $content ) {
 562      $current_priority = has_filter( 'the_content', '_restore_wpautop_hook' );
 563  
 564      add_filter( 'the_content', 'wpautop', $current_priority - 1 );
 565      remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority );
 566  
 567      return $content;
 568  }
 569  
 570  /**
 571   * Returns the current version of the block format that the content string is using.
 572   *
 573   * If the string doesn't contain blocks, it returns 0.
 574   *
 575   * @since 5.0.0
 576   *
 577   * @param string $content Content to test.
 578   * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise.
 579   */
 580  function block_version( $content ) {
 581      return has_blocks( $content ) ? 1 : 0;
 582  }
 583  
 584  /**
 585   * Registers a new block style.
 586   *
 587   * @since 5.3.0
 588   *
 589   * @param string $block_name       Block type name including namespace.
 590   * @param array  $style_properties Array containing the properties of the style name, label, style (name of the stylesheet to be enqueued), inline_style (string containing the CSS to be added).
 591   *
 592   * @return boolean True if the block style was registered with success and false otherwise.
 593   */
 594  function register_block_style( $block_name, $style_properties ) {
 595      return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties );
 596  }
 597  
 598  /**
 599   * Unregisters a block style.
 600   *
 601   * @since 5.3.0
 602   *
 603   * @param string $block_name       Block type name including namespace.
 604   * @param array  $block_style_name Block style name.
 605   *
 606   * @return boolean True if the block style was unregistered with success and false otherwise.
 607   */
 608  function unregister_block_style( $block_name, $block_style_name ) {
 609      return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name );
 610  }


Generated : Sun Apr 5 08:20:01 2020 Cross-referenced by PHPXref