88/**
99 * Render the custom CSS stylesheet and add class name to block as required.
1010 *
11- * @since 7.0.0
12- *
1311 * @param array $parsed_block The parsed block.
1412 * @return array The same parsed block with custom CSS class name added if appropriate.
1513 */
@@ -58,8 +56,6 @@ function gutenberg_render_custom_css_support_styles( $parsed_block ) {
5856
5957/**
6058 * Enqueues the block custom CSS styles.
61- *
62- * @since 7.0.0
6359 */
6460function gutenberg_enqueue_block_custom_css () {
6561 wp_enqueue_style ( 'wp-block-custom-css ' );
@@ -71,8 +67,6 @@ function gutenberg_enqueue_block_custom_css() {
7167 * The class name is generated in `gutenberg_render_custom_css_support_styles`
7268 * and stored in block attributes. This filter adds it to the actual markup.
7369 *
74- * @since 7.0.0
75- *
7670 * @param string $block_content Rendered block content.
7771 * @param array $block Block object.
7872 * @return string Filtered block content.
@@ -124,6 +118,142 @@ function gutenberg_register_custom_css_support( $block_type ) {
124118 }
125119}
126120
121+ /**
122+ * Strips `style.css` attributes from all blocks in post content.
123+ *
124+ * Uses WP_Block_Parser::next_token() to scan block tokens and surgically
125+ * replace only the attribute JSON that changed — no parse_blocks() +
126+ * serialize_blocks() round-trip needed.
127+ *
128+ * @param string $content Post content to filter, expected to be escaped with slashes.
129+ * @return string Filtered post content with block custom CSS removed.
130+ */
131+ function gutenberg_strip_custom_css_from_blocks ( $ content ) {
132+ if ( ! has_blocks ( $ content ) ) {
133+ return $ content ;
134+ }
135+
136+ $ unslashed = stripslashes ( $ content );
137+
138+ $ parser = new WP_Block_Parser ();
139+ $ parser ->document = $ unslashed ;
140+ $ parser ->offset = 0 ;
141+ $ end = strlen ( $ unslashed );
142+ $ replacements = array ();
143+
144+ while ( $ parser ->offset < $ end ) {
145+ $ next_token = $ parser ->next_token ();
146+ list ( $ token_type , , $ attrs , $ start_offset , $ token_length ) = $ next_token ;
147+
148+ if ( 'no-more-tokens ' === $ token_type ) {
149+ break ;
150+ }
151+
152+ $ parser ->offset = $ start_offset + $ token_length ;
153+
154+ if ( 'block-opener ' !== $ token_type && 'void-block ' !== $ token_type ) {
155+ continue ;
156+ }
157+
158+ if ( ! isset ( $ attrs ['style ' ]['css ' ] ) ) {
159+ continue ;
160+ }
161+
162+ // Remove css and clean up empty style.
163+ unset( $ attrs ['style ' ]['css ' ] );
164+ if ( empty ( $ attrs ['style ' ] ) ) {
165+ unset( $ attrs ['style ' ] );
166+ }
167+
168+ // Locate the JSON portion within the token.
169+ $ token_string = substr ( $ unslashed , $ start_offset , $ token_length );
170+ $ json_rel_start = strcspn ( $ token_string , '{ ' );
171+ $ json_rel_end = strrpos ( $ token_string , '} ' );
172+
173+ $ json_start = $ start_offset + $ json_rel_start ;
174+ $ json_length = $ json_rel_end - $ json_rel_start + 1 ;
175+
176+ // Re-encode attributes. If attrs is now empty, remove JSON and trailing space.
177+ if ( empty ( $ attrs ) ) {
178+ // Remove the trailing space after JSON: `{"style":{"css":"x"}} ` → ``
179+ $ replacements [] = array ( $ json_start , $ json_length + 1 , '' );
180+ } else {
181+ $ replacements [] = array ( $ json_start , $ json_length , serialize_block_attributes ( $ attrs ) );
182+ }
183+ }
184+
185+ if ( empty ( $ replacements ) ) {
186+ return $ content ;
187+ }
188+
189+ // Build the result by splicing replacements into the original string.
190+ $ result = '' ;
191+ $ was_at = 0 ;
192+
193+ foreach ( $ replacements as $ replacement ) {
194+ list ( $ offset , $ length , $ new_json ) = $ replacement ;
195+ $ result .= substr ( $ unslashed , $ was_at , $ offset - $ was_at ) . $ new_json ;
196+ $ was_at = $ offset + $ length ;
197+ }
198+
199+ if ( $ was_at < $ end ) {
200+ $ result .= substr ( $ unslashed , $ was_at );
201+ }
202+
203+ return addslashes ( $ result );
204+ }
205+
206+ /**
207+ * Adds the filters to strip custom CSS from block content on save.
208+ * @access private
209+ */
210+ function gutenberg_custom_css_kses_init_filters () {
211+ add_filter ( 'content_save_pre ' , 'gutenberg_strip_custom_css_from_blocks ' , 8 );
212+ add_filter ( 'content_filtered_save_pre ' , 'gutenberg_strip_custom_css_from_blocks ' , 8 );
213+ }
214+
215+ /**
216+ * Removes the filters that strip custom CSS from block content on save.
217+ * @access private
218+ */
219+ function gutenberg_custom_css_remove_filters () {
220+ remove_filter ( 'content_save_pre ' , 'gutenberg_strip_custom_css_from_blocks ' , 8 );
221+ remove_filter ( 'content_filtered_save_pre ' , 'gutenberg_strip_custom_css_from_blocks ' , 8 );
222+ }
223+
224+ /**
225+ * Registers the custom CSS content filters if the user does not have the edit_css capability.
226+ * @access private
227+ */
228+ function gutenberg_custom_css_kses_init () {
229+ gutenberg_custom_css_remove_filters ();
230+ if ( ! current_user_can ( 'edit_css ' ) ) {
231+ gutenberg_custom_css_kses_init_filters ();
232+ }
233+ }
234+
235+ /**
236+ * Initializes custom CSS content filters when imported data should be filtered.
237+ *
238+ * This filter is the last being executed on force_filtered_html_on_import.
239+ * If the input of the filter is true it means we are in an import situation and should
240+ * enable the custom CSS filters, independently of the user capabilities.
241+ * @access private
242+ *
243+ * @param mixed $arg Input argument of the filter.
244+ * @return mixed Input argument of the filter.
245+ */
246+ function gutenberg_custom_css_force_filtered_html_on_import_filter ( $ arg ) {
247+ if ( $ arg ) {
248+ gutenberg_custom_css_kses_init_filters ();
249+ }
250+ return $ arg ;
251+ }
252+
253+ add_action ( 'init ' , 'gutenberg_custom_css_kses_init ' , 20 );
254+ add_action ( 'set_current_user ' , 'gutenberg_custom_css_kses_init ' );
255+ add_filter ( 'force_filtered_html_on_import ' , 'gutenberg_custom_css_force_filtered_html_on_import_filter ' , 999 );
256+
127257// Register the block support.
128258WP_Block_Supports::get_instance ()->register (
129259 'custom-css ' ,
0 commit comments