@@ -74,11 +74,11 @@ function has_blocks( $post = null ) {
7474 * @since 5.0.0
7575 * @see parse_blocks()
7676 *
77- * @param string $block_type Full Block type to look for.
77+ * @param string $block_name Full Block type to look for.
7878 * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object. Defaults to global $post.
7979 * @return bool Whether the post content contains the specified block.
8080 */
81- function has_block ( $ block_type , $ post = null ) {
81+ function has_block ( $ block_name , $ post = null ) {
8282 if ( ! has_blocks ( $ post ) ) {
8383 return false ;
8484 }
@@ -90,7 +90,30 @@ function has_block( $block_type, $post = null ) {
9090 }
9191 }
9292
93- return false !== strpos ( $ post , '<!-- wp: ' . $ block_type . ' ' );
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 ;
94117}
95118
96119/**
@@ -113,6 +136,207 @@ function get_dynamic_block_names() {
113136 return $ dynamic_block_names ;
114137}
115138
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 mixed $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 array 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+
116340/**
117341 * Parses blocks out of a content string, and renders those appropriate for the excerpt.
118342 *
0 commit comments