blocks.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. * Registers a block type.
  11. *
  12. * @since 5.0.0
  13. *
  14. * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
  15. * complete WP_Block_Type instance. In case a WP_Block_Type
  16. * is provided, the $args parameter will be ignored.
  17. * @param array $args {
  18. * Optional. Array of block type arguments. Any arguments may be defined, however the
  19. * ones described below are supported by default. Default empty array.
  20. *
  21. * @type callable $render_callback Callback used to render blocks of this block type.
  22. * }
  23. * @return WP_Block_Type|false The registered block type on success, or false on failure.
  24. */
  25. function register_block_type( $name, $args = array() ) {
  26. return WP_Block_Type_Registry::get_instance()->register( $name, $args );
  27. }
  28. /**
  29. * Unregisters a block type.
  30. *
  31. * @since 5.0.0
  32. *
  33. * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
  34. * complete WP_Block_Type instance.
  35. * @return WP_Block_Type|false The unregistered block type on success, or false on failure.
  36. */
  37. function unregister_block_type( $name ) {
  38. return WP_Block_Type_Registry::get_instance()->unregister( $name );
  39. }
  40. /**
  41. * Determine whether a post or content string has blocks.
  42. *
  43. * This test optimizes for performance rather than strict accuracy, detecting
  44. * the pattern of a block but not validating its structure. For strict accuracy,
  45. * you should use the block parser on post content.
  46. *
  47. * @since 5.0.0
  48. * @see parse_blocks()
  49. *
  50. * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
  51. * @return bool Whether the post has blocks.
  52. */
  53. function has_blocks( $post = null ) {
  54. if ( ! is_string( $post ) ) {
  55. $wp_post = get_post( $post );
  56. if ( $wp_post instanceof WP_Post ) {
  57. $post = $wp_post->post_content;
  58. }
  59. }
  60. return false !== strpos( (string) $post, '<!-- wp:' );
  61. }
  62. /**
  63. * Determine whether a $post or a string contains a specific block type.
  64. *
  65. * This test optimizes for performance rather than strict accuracy, detecting
  66. * the block type exists but not validating its structure. For strict accuracy,
  67. * you should use the block parser on post content.
  68. *
  69. * @since 5.0.0
  70. * @see parse_blocks()
  71. *
  72. * @param string $block_type Full Block type to look for.
  73. * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
  74. * @return bool Whether the post content contains the specified block.
  75. */
  76. function has_block( $block_type, $post = null ) {
  77. if ( ! has_blocks( $post ) ) {
  78. return false;
  79. }
  80. if ( ! is_string( $post ) ) {
  81. $wp_post = get_post( $post );
  82. if ( $wp_post instanceof WP_Post ) {
  83. $post = $wp_post->post_content;
  84. }
  85. }
  86. return false !== strpos( $post, '<!-- wp:' . $block_type . ' ' );
  87. }
  88. /**
  89. * Returns an array of the names of all registered dynamic block types.
  90. *
  91. * @since 5.0.0
  92. *
  93. * @return array Array of dynamic block names.
  94. */
  95. function get_dynamic_block_names() {
  96. $dynamic_block_names = array();
  97. $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
  98. foreach ( $block_types as $block_type ) {
  99. if ( $block_type->is_dynamic() ) {
  100. $dynamic_block_names[] = $block_type->name;
  101. }
  102. }
  103. return $dynamic_block_names;
  104. }
  105. /**
  106. * Parses blocks out of a content string, and renders those appropriate for the excerpt.
  107. *
  108. * As the excerpt should be a small string of text relevant to the full post content,
  109. * this function renders the blocks that are most likely to contain such text.
  110. *
  111. * @since 5.0.0
  112. *
  113. * @param string $content The content to parse.
  114. * @return string The parsed and filtered content.
  115. */
  116. function excerpt_remove_blocks( $content ) {
  117. $allowed_inner_blocks = array(
  118. // Classic blocks have their blockName set to null.
  119. null,
  120. 'core/freeform',
  121. 'core/heading',
  122. 'core/html',
  123. 'core/list',
  124. 'core/media-text',
  125. 'core/paragraph',
  126. 'core/preformatted',
  127. 'core/pullquote',
  128. 'core/quote',
  129. 'core/table',
  130. 'core/verse',
  131. );
  132. $allowed_blocks = array_merge( $allowed_inner_blocks, array( 'core/columns' ) );
  133. /**
  134. * Filters the list of blocks that can contribute to the excerpt.
  135. *
  136. * If a dynamic block is added to this list, it must not generate another
  137. * excerpt, as this will cause an infinite loop to occur.
  138. *
  139. * @since 5.0.0
  140. *
  141. * @param array $allowed_blocks The list of allowed blocks.
  142. */
  143. $allowed_blocks = apply_filters( 'excerpt_allowed_blocks', $allowed_blocks );
  144. $blocks = parse_blocks( $content );
  145. $output = '';
  146. foreach ( $blocks as $block ) {
  147. if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
  148. if ( ! empty( $block['innerBlocks'] ) ) {
  149. if ( 'core/columns' === $block['blockName'] ) {
  150. $output .= _excerpt_render_inner_columns_blocks( $block, $allowed_inner_blocks );
  151. continue;
  152. }
  153. // Skip the block if it has disallowed or nested inner blocks.
  154. foreach ( $block['innerBlocks'] as $inner_block ) {
  155. if (
  156. ! in_array( $inner_block['blockName'], $allowed_inner_blocks, true ) ||
  157. ! empty( $inner_block['innerBlocks'] )
  158. ) {
  159. continue 2;
  160. }
  161. }
  162. }
  163. $output .= render_block( $block );
  164. }
  165. }
  166. return $output;
  167. }
  168. /**
  169. * Render inner blocks from the `core/columns` block for generating an excerpt.
  170. *
  171. * @since 5.2.0
  172. * @access private
  173. *
  174. * @param array $columns The parsed columns block.
  175. * @param array $allowed_blocks The list of allowed inner blocks.
  176. * @return string The rendered inner blocks.
  177. */
  178. function _excerpt_render_inner_columns_blocks( $columns, $allowed_blocks ) {
  179. $output = '';
  180. foreach ( $columns['innerBlocks'] as $column ) {
  181. foreach ( $column['innerBlocks'] as $inner_block ) {
  182. if ( in_array( $inner_block['blockName'], $allowed_blocks, true ) && empty( $inner_block['innerBlocks'] ) ) {
  183. $output .= render_block( $inner_block );
  184. }
  185. }
  186. }
  187. return $output;
  188. }
  189. /**
  190. * Renders a single block into a HTML string.
  191. *
  192. * @since 5.0.0
  193. *
  194. * @global WP_Post $post The post to edit.
  195. *
  196. * @param array $block A single parsed block object.
  197. * @return string String of rendered HTML.
  198. */
  199. function render_block( $block ) {
  200. global $post;
  201. /**
  202. * Allows render_block() to be shortcircuited, by returning a non-null value.
  203. *
  204. * @since 5.1.0
  205. *
  206. * @param string|null $pre_render The pre-rendered content. Default null.
  207. * @param array $block The block being rendered.
  208. */
  209. $pre_render = apply_filters( 'pre_render_block', null, $block );
  210. if ( ! is_null( $pre_render ) ) {
  211. return $pre_render;
  212. }
  213. $source_block = $block;
  214. /**
  215. * Filters the block being rendered in render_block(), before it's processed.
  216. *
  217. * @since 5.1.0
  218. *
  219. * @param array $block The block being rendered.
  220. * @param array $source_block An un-modified copy of $block, as it appeared in the source content.
  221. */
  222. $block = apply_filters( 'render_block_data', $block, $source_block );
  223. $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
  224. $is_dynamic = $block['blockName'] && null !== $block_type && $block_type->is_dynamic();
  225. $block_content = '';
  226. $index = 0;
  227. foreach ( $block['innerContent'] as $chunk ) {
  228. $block_content .= is_string( $chunk ) ? $chunk : render_block( $block['innerBlocks'][ $index++ ] );
  229. }
  230. if ( ! is_array( $block['attrs'] ) ) {
  231. $block['attrs'] = array();
  232. }
  233. if ( $is_dynamic ) {
  234. $global_post = $post;
  235. $block_content = $block_type->render( $block['attrs'], $block_content );
  236. $post = $global_post;
  237. }
  238. /**
  239. * Filters the content of a single block.
  240. *
  241. * @since 5.0.0
  242. *
  243. * @param string $block_content The block content about to be appended.
  244. * @param array $block The full block, including name and attributes.
  245. */
  246. return apply_filters( 'render_block', $block_content, $block );
  247. }
  248. /**
  249. * Parses blocks out of a content string.
  250. *
  251. * @since 5.0.0
  252. *
  253. * @param string $content Post content.
  254. * @return array Array of parsed block objects.
  255. */
  256. function parse_blocks( $content ) {
  257. /**
  258. * Filter to allow plugins to replace the server-side block parser
  259. *
  260. * @since 5.0.0
  261. *
  262. * @param string $parser_class Name of block parser class.
  263. */
  264. $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' );
  265. $parser = new $parser_class();
  266. return $parser->parse( $content );
  267. }
  268. /**
  269. * Parses dynamic blocks out of `post_content` and re-renders them.
  270. *
  271. * @since 5.0.0
  272. *
  273. * @param string $content Post content.
  274. * @return string Updated post content.
  275. */
  276. function do_blocks( $content ) {
  277. $blocks = parse_blocks( $content );
  278. $output = '';
  279. foreach ( $blocks as $block ) {
  280. $output .= render_block( $block );
  281. }
  282. // If there are blocks in this content, we shouldn't run wpautop() on it later.
  283. $priority = has_filter( 'the_content', 'wpautop' );
  284. if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
  285. remove_filter( 'the_content', 'wpautop', $priority );
  286. add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
  287. }
  288. return $output;
  289. }
  290. /**
  291. * If do_blocks() needs to remove wpautop() from the `the_content` filter, this re-adds it afterwards,
  292. * for subsequent `the_content` usage.
  293. *
  294. * @access private
  295. *
  296. * @since 5.0.0
  297. *
  298. * @param string $content The post content running through this filter.
  299. * @return string The unmodified content.
  300. */
  301. function _restore_wpautop_hook( $content ) {
  302. $current_priority = has_filter( 'the_content', '_restore_wpautop_hook' );
  303. add_filter( 'the_content', 'wpautop', $current_priority - 1 );
  304. remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority );
  305. return $content;
  306. }
  307. /**
  308. * Returns the current version of the block format that the content string is using.
  309. *
  310. * If the string doesn't contain blocks, it returns 0.
  311. *
  312. * @since 5.0.0
  313. *
  314. * @param string $content Content to test.
  315. * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise.
  316. */
  317. function block_version( $content ) {
  318. return has_blocks( $content ) ? 1 : 0;
  319. }
  320. /**
  321. * Registers a new block style.
  322. *
  323. * @since 5.3.0
  324. *
  325. * @param string $block_name Block type name including namespace.
  326. * @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).
  327. *
  328. * @return boolean True if the block style was registered with success and false otherwise.
  329. */
  330. function register_block_style( $block_name, $style_properties ) {
  331. return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties );
  332. }
  333. /**
  334. * Unregisters a block style.
  335. *
  336. * @since 5.3.0
  337. *
  338. * @param string $block_name Block type name including namespace.
  339. * @param array $block_style_name Block style name.
  340. *
  341. * @return boolean True if the block style was unregistered with success and false otherwise.
  342. */
  343. function unregister_block_style( $block_name, $block_style_name ) {
  344. return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name );
  345. }