| @@ -1,166 +1,271 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Block and style registration functions. | |
| 3 | + * Functions related to editor blocks for the Gutenberg editor plugin. | |
| 4 | 4 | * |
| 5 | 5 | * @package gutenberg |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | -/** | |
| 9 | - * Retrieves registered social link blocks | |
| 10 | - * | |
| 11 | - * @return array Array of strings containing the registered social link block names. | |
| 12 | - */ | |
| 13 | -function gutenberg_get_registered_social_link_blocks() { | |
| 14 | - $social_link_prefix = 'core/social-link'; | |
| 15 | - $social_link_prefix_length = strlen( $social_link_prefix ); | |
| 8 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 9 | + die( 'Silence is golden.' ); | |
| 10 | +} | |
| 16 | 11 | |
| 17 | - $registry = WP_Block_Type_Registry::get_instance(); | |
| 18 | - $block_types = $registry->get_all_registered(); | |
| 12 | +if ( ! function_exists( 'register_block_type' ) ) { | |
| 13 | + /** | |
| 14 | + * Registers a block type. | |
| 15 | + * | |
| 16 | + * @since 0.1.0 | |
| 17 | + * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter. | |
| 18 | + * | |
| 19 | + * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a | |
| 20 | + * complete WP_Block_Type instance. In case a WP_Block_Type | |
| 21 | + * is provided, the $args parameter will be ignored. | |
| 22 | + * @param array $args { | |
| 23 | + * Optional. Array of block type arguments. Any arguments may be defined, however the | |
| 24 | + * ones described below are supported by default. Default empty array. | |
| 25 | + * | |
| 26 | + * @type callable $render_callback Callback used to render blocks of this block type. | |
| 27 | + * } | |
| 28 | + * @return WP_Block_Type|false The registered block type on success, or false on failure. | |
| 29 | + */ | |
| 30 | + function register_block_type( $name, $args = array() ) { | |
| 31 | + return WP_Block_Type_Registry::get_instance()->register( $name, $args ); | |
| 32 | + } | |
| 33 | +} | |
| 19 | 34 | |
| 20 | - $registered_social_link_blocks = array(); | |
| 21 | - foreach ( $block_types as $block_type ) { | |
| 22 | - // Block type name starts with $social_link_prefix. | |
| 23 | - if ( strncmp( $block_type->name, $social_link_prefix, $social_link_prefix_length ) === 0 ) { | |
| 24 | - $registered_social_link_blocks[] = $block_type->name; | |
| 35 | +if ( ! function_exists( 'unregister_block_type' ) ) { | |
| 36 | + /** | |
| 37 | + * Unregisters a block type. | |
| 38 | + * | |
| 39 | + * @since 0.1.0 | |
| 40 | + * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter. | |
| 41 | + * | |
| 42 | + * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a | |
| 43 | + * complete WP_Block_Type instance. | |
| 44 | + * @return WP_Block_Type|false The unregistered block type on success, or false on failure. | |
| 45 | + */ | |
| 46 | + function unregister_block_type( $name ) { | |
| 47 | + return WP_Block_Type_Registry::get_instance()->unregister( $name ); | |
| 48 | + } | |
| 49 | +} | |
| 50 | + | |
| 51 | +if ( ! function_exists( 'gutenberg_parse_blocks' ) ) { | |
| 52 | + /** | |
| 53 | + * Parses blocks out of a content string. | |
| 54 | + * | |
| 55 | + * @since 0.5.0 | |
| 56 | + * | |
| 57 | + * @param string $content Post content. | |
| 58 | + * @return array Array of parsed block objects. | |
| 59 | + */ | |
| 60 | + function gutenberg_parse_blocks( $content ) { | |
| 61 | + /** | |
| 62 | + * Filter to allow plugins to replace the server-side block parser | |
| 63 | + * | |
| 64 | + * @since 3.8.0 | |
| 65 | + * | |
| 66 | + * @param string $parser_class Name of block parser class | |
| 67 | + */ | |
| 68 | + $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' ); | |
| 69 | + // Load default block parser for server-side parsing if the default parser class is being used. | |
| 70 | + if ( 'WP_Block_Parser' === $parser_class ) { | |
| 71 | + require_once dirname( __FILE__ ) . '/../packages/block-serialization-default-parser/parser.php'; | |
| 25 | 72 | } |
| 73 | + $parser = new $parser_class(); | |
| 74 | + return $parser->parse( $content ); | |
| 26 | 75 | } |
| 27 | - return $registered_social_link_blocks; | |
| 28 | 76 | } |
| 29 | 77 | |
| 78 | +if ( ! function_exists( 'get_dynamic_block_names' ) ) { | |
| 79 | + /** | |
| 80 | + * Returns an array of the names of all registered dynamic block types. | |
| 81 | + * | |
| 82 | + * @return array Array of dynamic block names. | |
| 83 | + */ | |
| 84 | + function get_dynamic_block_names() { | |
| 85 | + $dynamic_block_names = array(); | |
| 86 | + | |
| 87 | + $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); | |
| 88 | + foreach ( $block_types as $block_type ) { | |
| 89 | + if ( $block_type->is_dynamic() ) { | |
| 90 | + $dynamic_block_names[] = $block_type->name; | |
| 91 | + } | |
| 92 | + } | |
| 93 | + | |
| 94 | + return $dynamic_block_names; | |
| 95 | + } | |
| 96 | +} | |
| 97 | + | |
| 98 | +if ( ! function_exists( 'get_dynamic_blocks_regex' ) ) { | |
| 99 | + /** | |
| 100 | + * Retrieve the dynamic blocks regular expression for searching. | |
| 101 | + * | |
| 102 | + * @since 3.6.0 | |
| 103 | + * | |
| 104 | + * @return string | |
| 105 | + */ | |
| 106 | + function get_dynamic_blocks_regex() { | |
| 107 | + $dynamic_block_names = get_dynamic_block_names(); | |
| 108 | + $dynamic_block_pattern = ( | |
| 109 | + '/<!--\s+wp:(' . | |
| 110 | + str_replace( | |
| 111 | + '/', | |
| 112 | + '\/', // Escape namespace, not handled by preg_quote. | |
| 113 | + str_replace( | |
| 114 | + 'core/', | |
| 115 | + '(?:core/)?', // Allow implicit core namespace, but don't capture. | |
| 116 | + implode( | |
| 117 | + '|', // Join block names into capture group alternation. | |
| 118 | + array_map( | |
| 119 | + 'preg_quote', // Escape block name for regular expression. | |
| 120 | + $dynamic_block_names | |
| 121 | + ) | |
| 122 | + ) | |
| 123 | + ) | |
| 124 | + ) . | |
| 125 | + ')(\s+(\{.*?\}))?\s+(\/)?-->/' | |
| 126 | + ); | |
| 127 | + | |
| 128 | + return $dynamic_block_pattern; | |
| 129 | + } | |
| 130 | +} | |
| 131 | + | |
| 30 | 132 | /** |
| 31 | - * Substitutes the implementation of a core-registered block type, if exists, | |
| 32 | - * with the built result from the plugin. | |
| 133 | + * Renders a single block into a HTML string. | |
| 134 | + * | |
| 135 | + * @since 1.9.0 | |
| 136 | + * @since 4.4.0 renders full nested tree of blocks before reassembling into HTML string | |
| 137 | + * @global WP_Post $post The post to edit. | |
| 138 | + * | |
| 139 | + * @param array $block A single parsed block object. | |
| 140 | + * @return string String of rendered HTML. | |
| 33 | 141 | */ |
| 34 | -function gutenberg_reregister_core_block_types() { | |
| 35 | - // Blocks directory may not exist if working from a fresh clone. | |
| 36 | - $blocks_dir = dirname( __FILE__ ) . '/../build/block-library/blocks/'; | |
| 37 | - if ( ! file_exists( $blocks_dir ) ) { | |
| 38 | - return; | |
| 142 | +function gutenberg_render_block( $block ) { | |
| 143 | + global $post; | |
| 144 | + | |
| 145 | + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); | |
| 146 | + $is_dynamic = $block['blockName'] && null !== $block_type && $block_type->is_dynamic(); | |
| 147 | + $inner_content = ''; | |
| 148 | + $index = 0; | |
| 149 | + | |
| 150 | + foreach ( $block['innerContent'] as $chunk ) { | |
| 151 | + $inner_content .= is_string( $chunk ) ? $chunk : gutenberg_render_block( $block['innerBlocks'][ $index++ ] ); | |
| 39 | 152 | } |
| 40 | 153 | |
| 41 | - $block_names = array( | |
| 42 | - 'archives.php' => 'core/archives', | |
| 43 | - 'block.php' => 'core/block', | |
| 44 | - 'calendar.php' => 'core/calendar', | |
| 45 | - 'categories.php' => 'core/categories', | |
| 46 | - 'latest-comments.php' => 'core/latest-comments', | |
| 47 | - 'latest-posts.php' => 'core/latest-posts', | |
| 48 | - 'legacy-widget.php' => 'core/legacy-widget', | |
| 49 | - 'navigation.php' => 'core/navigation', | |
| 50 | - 'rss.php' => 'core/rss', | |
| 51 | - 'shortcode.php' => 'core/shortcode', | |
| 52 | - 'search.php' => 'core/search', | |
| 53 | - 'social-link.php' => gutenberg_get_registered_social_link_blocks(), | |
| 54 | - 'tag-cloud.php' => 'core/tag-cloud', | |
| 55 | - 'site-title.php' => 'core/site-title', | |
| 56 | - 'template-part.php' => 'core/template-part', | |
| 57 | - 'post-title.php' => 'core/post-title', | |
| 58 | - 'post-content.php' => 'core/post-content', | |
| 59 | - 'post-author.php' => 'core/post-author', | |
| 60 | - 'post-date.php' => 'core/post-date', | |
| 61 | - 'post-excerpt.php' => 'core/post-excerpt', | |
| 62 | - ); | |
| 154 | + if ( $is_dynamic ) { | |
| 155 | + $attributes = is_array( $block['attrs'] ) ? (array) $block['attrs'] : array(); | |
| 156 | + $global_post = $post; | |
| 157 | + $output = $block_type->render( $attributes, $inner_content ); | |
| 158 | + $post = $global_post; | |
| 63 | 159 | |
| 64 | - $registry = WP_Block_Type_Registry::get_instance(); | |
| 160 | + return $output; | |
| 161 | + } | |
| 65 | 162 | |
| 66 | - foreach ( $block_names as $file => $block_names ) { | |
| 67 | - if ( ! file_exists( $blocks_dir . $file ) ) { | |
| 68 | - return; | |
| 163 | + return $inner_content; | |
| 164 | +} | |
| 165 | + | |
| 166 | +if ( ! function_exists( 'do_blocks' ) ) { | |
| 167 | + /** | |
| 168 | + * Parses dynamic blocks out of `post_content` and re-renders them. | |
| 169 | + * | |
| 170 | + * @since 0.1.0 | |
| 171 | + * @since 4.4.0 performs full parse on input post content | |
| 172 | + * | |
| 173 | + * @param string $content Post content. | |
| 174 | + * @return string Updated post content. | |
| 175 | + */ | |
| 176 | + function do_blocks( $content ) { | |
| 177 | + // If there are blocks in this content, we shouldn't run wpautop() on it later. | |
| 178 | + $priority = has_filter( 'the_content', 'wpautop' ); | |
| 179 | + if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) { | |
| 180 | + remove_filter( 'the_content', 'wpautop', $priority ); | |
| 181 | + add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 ); | |
| 69 | 182 | } |
| 70 | 183 | |
| 71 | - if ( is_string( $block_names ) ) { | |
| 72 | - if ( $registry->is_registered( $block_names ) ) { | |
| 73 | - $registry->unregister( $block_names ); | |
| 74 | - } | |
| 75 | - } elseif ( is_array( $block_names ) ) { | |
| 76 | - foreach ( $block_names as $block_name ) { | |
| 77 | - if ( $registry->is_registered( $block_name ) ) { | |
| 78 | - $registry->unregister( $block_name ); | |
| 79 | - } | |
| 80 | - } | |
| 184 | + $blocks = gutenberg_parse_blocks( $content ); | |
| 185 | + $output = ''; | |
| 186 | + | |
| 187 | + foreach ( $blocks as $block ) { | |
| 188 | + $output .= gutenberg_render_block( $block ); | |
| 81 | 189 | } |
| 82 | 190 | |
| 83 | - require $blocks_dir . $file; | |
| 191 | + return $output; | |
| 84 | 192 | } |
| 193 | + | |
| 194 | + add_filter( 'the_content', 'do_blocks', 7 ); // BEFORE do_shortcode() and oembed. | |
| 85 | 195 | } |
| 86 | -add_action( 'init', 'gutenberg_reregister_core_block_types' ); | |
| 87 | 196 | |
| 88 | -if ( ! function_exists( 'register_block_style' ) ) { | |
| 197 | +if ( ! function_exists( '_restore_wpautop_hook' ) ) { | |
| 89 | 198 | /** |
| 90 | - * Registers a new block style. | |
| 199 | + * If do_blocks() needs to remove wpautop() from the `the_content` filter, | |
| 200 | + * this re-adds it afterwards, for subsequent `the_content` usage. | |
| 91 | 201 | * |
| 92 | - * @param string $block_name Block type name including namespace. | |
| 93 | - * @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). | |
| 202 | + * @access private | |
| 94 | 203 | * |
| 95 | - * @return boolean True if the block style was registered with success and false otherwise. | |
| 204 | + * @since 4.6.0 | |
| 205 | + * | |
| 206 | + * @param string $content The post content running through this filter. | |
| 207 | + * @return string The unmodified content. | |
| 96 | 208 | */ |
| 97 | - function register_block_style( $block_name, $style_properties ) { | |
| 98 | - return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties ); | |
| 209 | + function _restore_wpautop_hook( $content ) { | |
| 210 | + $current_priority = has_filter( 'the_content', '_restore_wpautop_hook' ); | |
| 211 | + | |
| 212 | + add_filter( 'the_content', 'wpautop', $current_priority - 1 ); | |
| 213 | + remove_filter( 'the_content', '_restore_wpautop_hook', $current_priority ); | |
| 214 | + | |
| 215 | + return $content; | |
| 99 | 216 | } |
| 100 | 217 | } |
| 101 | 218 | |
| 102 | -if ( ! function_exists( 'unregister_block_style' ) ) { | |
| 219 | +if ( ! function_exists( 'strip_dynamic_blocks' ) ) { | |
| 103 | 220 | /** |
| 104 | - * Unregisters a block style. | |
| 221 | + * Remove all dynamic blocks from the given content. | |
| 105 | 222 | * |
| 106 | - * @param string $block_name Block type name including namespace. | |
| 107 | - * @param array $block_style_name Block style name. | |
| 223 | + * @since 3.6.0 | |
| 108 | 224 | * |
| 109 | - * @return boolean True if the block style was unregistered with success and false otherwise. | |
| 225 | + * @param string $content Content of the current post. | |
| 226 | + * @return string | |
| 110 | 227 | */ |
| 111 | - function unregister_block_style( $block_name, $block_style_name ) { | |
| 112 | - return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name ); | |
| 228 | + function strip_dynamic_blocks( $content ) { | |
| 229 | + return preg_replace( get_dynamic_blocks_regex(), '', $content ); | |
| 113 | 230 | } |
| 114 | 231 | } |
| 115 | 232 | |
| 116 | -if ( ! has_action( 'enqueue_block_assets', 'enqueue_block_styles_assets' ) ) { | |
| 233 | +if ( ! function_exists( 'strip_dynamic_blocks_add_filter' ) ) { | |
| 117 | 234 | /** |
| 118 | - * Function responsible for enqueuing the styles required for block styles functionality on the editor and on the frontend. | |
| 235 | + * Adds the content filter to strip dynamic blocks from excerpts. | |
| 236 | + * | |
| 237 | + * It's a bit hacky for now, but once this gets merged into core the function | |
| 238 | + * can just be called in `wp_trim_excerpt()`. | |
| 239 | + * | |
| 240 | + * @since 3.6.0 | |
| 241 | + * | |
| 242 | + * @param string $text Excerpt. | |
| 243 | + * @return string | |
| 119 | 244 | */ |
| 120 | - function gutenberg_enqueue_block_styles_assets() { | |
| 121 | - $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); | |
| 245 | + function strip_dynamic_blocks_add_filter( $text ) { | |
| 246 | + add_filter( 'the_content', 'strip_dynamic_blocks', 6 ); | |
| 122 | 247 | |
| 123 | - foreach ( $block_styles as $styles ) { | |
| 124 | - foreach ( $styles as $style_properties ) { | |
| 125 | - if ( isset( $style_properties['style_handle'] ) ) { | |
| 126 | - wp_enqueue_style( $style_properties['style_handle'] ); | |
| 127 | - } | |
| 128 | - if ( isset( $style_properties['inline_style'] ) ) { | |
| 129 | - wp_add_inline_style( 'wp-block-library', $style_properties['inline_style'] ); | |
| 130 | - } | |
| 131 | - } | |
| 132 | - } | |
| 248 | + return $text; | |
| 133 | 249 | } |
| 134 | - add_action( 'enqueue_block_assets', 'gutenberg_enqueue_block_styles_assets', 30 ); | |
| 250 | + add_filter( 'get_the_excerpt', 'strip_dynamic_blocks_add_filter', 9 ); // Before wp_trim_excerpt(). | |
| 135 | 251 | } |
| 136 | -if ( ! has_action( 'enqueue_block_editor_assets', 'enqueue_editor_block_styles_assets' ) ) { | |
| 252 | + | |
| 253 | +if ( ! function_exists( 'strip_dynamic_blocks_remove_filter' ) ) { | |
| 137 | 254 | /** |
| 138 | - * Function responsible for enqueuing the assets required for block styles functionality on the editor. | |
| 255 | + * Removes the content filter to strip dynamic blocks from excerpts. | |
| 256 | + * | |
| 257 | + * It's a bit hacky for now, but once this gets merged into core the function | |
| 258 | + * can just be called in `wp_trim_excerpt()`. | |
| 259 | + * | |
| 260 | + * @since 3.6.0 | |
| 261 | + * | |
| 262 | + * @param string $text Excerpt. | |
| 263 | + * @return string | |
| 139 | 264 | */ |
| 140 | - function gutenberg_enqueue_editor_block_styles_assets() { | |
| 141 | - $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); | |
| 265 | + function strip_dynamic_blocks_remove_filter( $text ) { | |
| 266 | + remove_filter( 'the_content', 'strip_dynamic_blocks', 6 ); | |
| 142 | 267 | |
| 143 | - $register_script_lines = array( '( function() {' ); | |
| 144 | - foreach ( $block_styles as $block_name => $styles ) { | |
| 145 | - foreach ( $styles as $style_properties ) { | |
| 146 | - $register_script_lines[] = sprintf( | |
| 147 | - ' wp.blocks.registerBlockStyle( \'%s\', %s );', | |
| 148 | - $block_name, | |
| 149 | - wp_json_encode( | |
| 150 | - array( | |
| 151 | - 'name' => $style_properties['name'], | |
| 152 | - 'label' => $style_properties['label'], | |
| 153 | - ) | |
| 154 | - ) | |
| 155 | - ); | |
| 156 | - } | |
| 157 | - } | |
| 158 | - $register_script_lines[] = '} )();'; | |
| 159 | - $inline_script = implode( "\n", $register_script_lines ); | |
| 160 | - | |
| 161 | - wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, true ); | |
| 162 | - wp_add_inline_script( 'wp-block-styles', $inline_script ); | |
| 163 | - wp_enqueue_script( 'wp-block-styles' ); | |
| 268 | + return $text; | |
| 164 | 269 | } |
| 165 | - add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_editor_block_styles_assets' ); | |
| 270 | + add_filter( 'wp_trim_excerpt', 'strip_dynamic_blocks_remove_filter', 0 ); // Before all other. | |
| 166 | 271 | } |