PluginProbe
Gutenberg / 4.0.1
Gutenberg v4.0.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / blocks.php

blocks.php in Gutenberg 4.0.1, at lib/blocks.php

303 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions related to editor blocks for the Gutenberg editor plugin.
4 *
5 * @package gutenberg
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 die( 'Silence is golden.' );
10 }
11
12 /**
13 * Registers a block type.
14 *
15 * @since 0.1.0
16 * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter.
17 *
18 * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
19 * complete WP_Block_Type instance. In case a WP_Block_Type
20 * is provided, the $args parameter will be ignored.
21 * @param array $args {
22 * Optional. Array of block type arguments. Any arguments may be defined, however the
23 * ones described below are supported by default. Default empty array.
24 *
25 * @type callable $render_callback Callback used to render blocks of this block type.
26 * }
27 * @return WP_Block_Type|false The registered block type on success, or false on failure.
28 */
29 function register_block_type( $name, $args = array() ) {
30 return WP_Block_Type_Registry::get_instance()->register( $name, $args );
31 }
32
33 /**
34 * Unregisters a block type.
35 *
36 * @since 0.1.0
37 * @since 0.6.0 Now also accepts a WP_Block_Type instance as first parameter.
38 *
39 * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
40 * complete WP_Block_Type instance.
41 * @return WP_Block_Type|false The unregistered block type on success, or false on failure.
42 */
43 function unregister_block_type( $name ) {
44 return WP_Block_Type_Registry::get_instance()->unregister( $name );
45 }
46
47 /**
48 * Parses blocks out of a content string.
49 *
50 * @since 0.5.0
51 *
52 * @param string $content Post content.
53 * @return array Array of parsed block objects.
54 */
55 function gutenberg_parse_blocks( $content ) {
56 /*
57 * If there are no blocks in the content, return a single block, rather
58 * than wasting time trying to parse the string.
59 */
60 if ( ! has_blocks( $content ) ) {
61 return array(
62 array(
63 'blockName' => null,
64 'attrs' => array(),
65 'innerBlocks' => array(),
66 'innerHTML' => $content,
67 ),
68 );
69 }
70
71 /**
72 * Filter to allow plugins to replace the server-side block parser
73 *
74 * @since 3.8.0
75 *
76 * @param string $parser_class Name of block parser class
77 */
78 $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' );
79 // Load default block parser for server-side parsing if the default parser class is being used.
80 if ( 'WP_Block_Parser' === $parser_class ) {
81 require_once dirname( __FILE__ ) . '/../packages/block-serialization-default-parser/parser.php';
82 }
83 $parser = new $parser_class();
84 return $parser->parse( $content );
85 }
86
87 /**
88 * Returns an array of the names of all registered dynamic block types.
89 *
90 * @return array Array of dynamic block names.
91 */
92 function get_dynamic_block_names() {
93 $dynamic_block_names = array();
94
95 $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
96 foreach ( $block_types as $block_type ) {
97 if ( $block_type->is_dynamic() ) {
98 $dynamic_block_names[] = $block_type->name;
99 }
100 }
101
102 return $dynamic_block_names;
103 }
104
105 /**
106 * Retrieve the dynamic blocks regular expression for searching.
107 *
108 * @since 3.6.0
109 *
110 * @return string
111 */
112 function get_dynamic_blocks_regex() {
113 $dynamic_block_names = get_dynamic_block_names();
114 $dynamic_block_pattern = (
115 '/<!--\s+wp:(' .
116 str_replace(
117 '/',
118 '\/', // Escape namespace, not handled by preg_quote.
119 str_replace(
120 'core/',
121 '(?:core/)?', // Allow implicit core namespace, but don't capture.
122 implode(
123 '|', // Join block names into capture group alternation.
124 array_map(
125 'preg_quote', // Escape block name for regular expression.
126 $dynamic_block_names
127 )
128 )
129 )
130 ) .
131 ')(\s+(\{.*?\}))?\s+(\/)?-->/'
132 );
133
134 return $dynamic_block_pattern;
135 }
136
137 /**
138 * Renders a single block into a HTML string.
139 *
140 * @since 1.9.0
141 *
142 * @param array $block A single parsed block object.
143 * @return string String of rendered HTML.
144 */
145 function gutenberg_render_block( $block ) {
146 $block_name = isset( $block['blockName'] ) ? $block['blockName'] : null;
147 $attributes = is_array( $block['attrs'] ) ? $block['attrs'] : array();
148 $raw_content = isset( $block['innerHTML'] ) ? $block['innerHTML'] : null;
149
150 if ( $block_name ) {
151 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
152 if ( null !== $block_type && $block_type->is_dynamic() ) {
153 return $block_type->render( $attributes );
154 }
155 }
156
157 if ( $raw_content ) {
158 return $raw_content;
159 }
160
161 return '';
162 }
163
164 /**
165 * Parses dynamic blocks out of `post_content` and re-renders them.
166 *
167 * @since 0.1.0
168 * @global WP_Post $post The post to edit.
169 *
170 * @param string $content Post content.
171 * @return string Updated post content.
172 */
173 function do_blocks( $content ) {
174 global $post;
175
176 $rendered_content = '';
177 $dynamic_block_pattern = get_dynamic_blocks_regex();
178
179 /*
180 * Back up global post, to restore after render callback.
181 * Allows callbacks to run new WP_Query instances without breaking the global post.
182 */
183 $global_post = $post;
184
185 while ( preg_match( $dynamic_block_pattern, $content, $block_match, PREG_OFFSET_CAPTURE ) ) {
186 $opening_tag = $block_match[0][0];
187 $offset = $block_match[0][1];
188 $block_name = $block_match[1][0];
189 $is_self_closing = isset( $block_match[4] );
190
191 // Reset attributes JSON to prevent scope bleed from last iteration.
192 $block_attributes_json = null;
193 if ( isset( $block_match[3] ) ) {
194 $block_attributes_json = $block_match[3][0];
195 }
196
197 // Since content is a working copy since the last match, append to
198 // rendered content up to the matched offset...
199 $rendered_content .= substr( $content, 0, $offset );
200
201 // ...then update the working copy of content.
202 $content = substr( $content, $offset + strlen( $opening_tag ) );
203
204 // Make implicit core namespace explicit.
205 $is_implicit_core_namespace = ( false === strpos( $block_name, '/' ) );
206 $normalized_block_name = $is_implicit_core_namespace ? 'core/' . $block_name : $block_name;
207
208 // Find registered block type. We can assume it exists since we use the
209 // `get_dynamic_block_names` function as a source for pattern matching.
210 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $normalized_block_name );
211
212 // Attempt to parse attributes JSON, if available.
213 $attributes = array();
214 if ( ! empty( $block_attributes_json ) ) {
215 $decoded_attributes = json_decode( $block_attributes_json, true );
216 if ( ! is_null( $decoded_attributes ) ) {
217 $attributes = $decoded_attributes;
218 }
219 }
220
221 $inner_content = '';
222
223 if ( ! $is_self_closing ) {
224 $end_tag_pattern = '/<!--\s+\/wp:' . str_replace( '/', '\/', preg_quote( $block_name ) ) . '\s+-->/';
225 if ( ! preg_match( $end_tag_pattern, $content, $block_match_end, PREG_OFFSET_CAPTURE ) ) {
226 // If no closing tag is found, abort all matching, and continue
227 // to append remainder of content to rendered output.
228 break;
229 }
230
231 // Update content to omit text up to and including closing tag.
232 $end_tag = $block_match_end[0][0];
233 $end_offset = $block_match_end[0][1];
234
235 $inner_content = substr( $content, 0, $end_offset );
236 $content = substr( $content, $end_offset + strlen( $end_tag ) );
237 }
238
239 // Replace dynamic block with server-rendered output.
240 $rendered_content .= $block_type->render( $attributes, $inner_content );
241
242 // Restore global $post.
243 $post = $global_post;
244 }
245
246 // Append remaining unmatched content.
247 $rendered_content .= $content;
248
249 // Strip remaining block comment demarcations.
250 $rendered_content = preg_replace( '/<!--\s+\/?wp:.*?-->\r?\n?/m', '', $rendered_content );
251
252 return $rendered_content;
253 }
254 add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode().
255
256 /**
257 * Remove all dynamic blocks from the given content.
258 *
259 * @since 3.6.0
260 *
261 * @param string $content Content of the current post.
262 * @return string
263 */
264 function strip_dynamic_blocks( $content ) {
265 return preg_replace( get_dynamic_blocks_regex(), '', $content );
266 }
267
268 /**
269 * Adds the content filter to strip dynamic blocks from excerpts.
270 *
271 * It's a bit hacky for now, but once this gets merged into core the function
272 * can just be called in `wp_trim_excerpt()`.
273 *
274 * @since 3.6.0
275 *
276 * @param string $text Excerpt.
277 * @return string
278 */
279 function strip_dynamic_blocks_add_filter( $text ) {
280 add_filter( 'the_content', 'strip_dynamic_blocks', 8 ); // Before do_blocks().
281
282 return $text;
283 }
284 add_filter( 'get_the_excerpt', 'strip_dynamic_blocks_add_filter', 9 ); // Before wp_trim_excerpt().
285
286 /**
287 * Removes the content filter to strip dynamic blocks from excerpts.
288 *
289 * It's a bit hacky for now, but once this gets merged into core the function
290 * can just be called in `wp_trim_excerpt()`.
291 *
292 * @since 3.6.0
293 *
294 * @param string $text Excerpt.
295 * @return string
296 */
297 function strip_dynamic_blocks_remove_filter( $text ) {
298 remove_filter( 'the_content', 'strip_dynamic_blocks', 8 );
299
300 return $text;
301 }
302 add_filter( 'wp_trim_excerpt', 'strip_dynamic_blocks_remove_filter', 0 ); // Before all other.
303