Changeset 64676
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/block-supports/custom-css.php
r64353 r64676 122 122 123 123 return $processed_css; 124 } 125 126 /** 127 * Generates the custom CSS class name for a block and registers its stylesheet. 128 * 129 * @since 7.2.0 130 * @access private 131 * 132 * @param array $parsed_block The parsed block. 133 * @return string|null The generated class name, or null if the block has no custom CSS to render. 134 * 135 * @phpstan-param array{ 136 * blockName: string|null, 137 * attrs: array{ 138 * className?: string, 139 * style?: array{ 140 * css?: string, 141 * ... 142 * }, 143 * ... 144 * }, 145 * ... 146 * } $parsed_block 147 */ 148 function wp_generate_custom_css_class_name( $parsed_block ) { 149 $style = $parsed_block['attrs']['style'] ?? null; 150 if ( empty( $style ) || ! is_array( $style ) ) { 151 return null; 152 } 153 154 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); 155 if ( ! block_has_support( $block_type, 'customCSS', true ) ) { 156 return null; 157 } 158 159 $viewport_settings = wp_get_global_settings( array( 'viewport' ) ); 160 $responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings ); 161 $state_entries = wp_get_custom_css_state_entries( $style, $parsed_block['blockName'], $responsive_media_queries ); 162 if ( empty( $state_entries ) ) { 163 return null; 164 } 165 166 // Generate a unique class name for this block instance. 167 $class_name = wp_unique_id_from_values( $parsed_block, 'wp-custom-css-' ); 168 $processed_css = wp_process_custom_css_state_entries( $state_entries, '.' . $class_name ); 169 if ( '' === $processed_css ) { 170 return null; 171 } 172 173 /** 174 * Reuse one handle so identical custom CSS is enqueued only once via 175 * {@see wp_unique_id_from_values()}. Explicitly declare the `wp-block-library` 176 * dependency so `global-styles` is guaranteed to print after it, preventing 177 * block default styles from unintentionally overriding global styles. 178 */ 179 $handle = 'wp-block-custom-css'; 180 if ( ! wp_style_is( $handle, 'registered' ) ) { 181 wp_register_style( $handle, false, array( 'wp-block-library', 'global-styles' ) ); 182 } 183 $after_styles = wp_styles()->get_data( $handle, 'after' ); 184 if ( ! is_array( $after_styles ) ) { 185 $after_styles = array(); 186 } 187 if ( ! in_array( $processed_css, $after_styles, true ) ) { 188 wp_add_inline_style( $handle, $processed_css ); 189 } 190 191 return $class_name; 124 192 } 125 193 … … 158 226 */ 159 227 function wp_render_custom_css_support_styles( $parsed_block ) { 160 $style = $parsed_block['attrs']['style'] ?? null; 161 if ( empty( $style ) || ! is_array( $style ) ) { 162 return $parsed_block; 163 } 164 165 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); 166 if ( ! block_has_support( $block_type, 'customCSS', true ) ) { 167 return $parsed_block; 168 } 169 170 $viewport_settings = wp_get_global_settings( array( 'viewport' ) ); 171 $responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings ); 172 $state_entries = wp_get_custom_css_state_entries( $style, $parsed_block['blockName'], $responsive_media_queries ); 173 if ( empty( $state_entries ) ) { 174 return $parsed_block; 175 } 176 177 // Generate a unique class name for this block instance. 178 $class_name = wp_unique_id_from_values( $parsed_block, 'wp-custom-css-' ); 179 $processed_css = wp_process_custom_css_state_entries( $state_entries, '.' . $class_name ); 180 if ( '' === $processed_css ) { 228 $class_name = wp_generate_custom_css_class_name( $parsed_block ); 229 if ( null === $class_name ) { 181 230 return $parsed_block; 182 231 } … … 189 238 $parsed_block['attrs']['className'] = $updated_class_name; 190 239 191 /**192 * Reuse one handle so identical custom CSS is enqueued only once via193 * {@see wp_unique_id_from_values()}. Explicitly declare the `wp-block-library`194 * dependency so `global-styles` is guaranteed to print after it, preventing195 * block default styles from unintentionally overriding global styles.196 */197 $handle = 'wp-block-custom-css';198 if ( ! wp_style_is( $handle, 'registered' ) ) {199 wp_register_style( $handle, false, array( 'wp-block-library', 'global-styles' ) );200 }201 $after_styles = wp_styles()->get_data( $handle, 'after' );202 if ( ! is_array( $after_styles ) ) {203 $after_styles = array();204 }205 if ( ! in_array( $processed_css, $after_styles, true ) ) {206 wp_add_inline_style( $handle, $processed_css );207 }208 209 240 return $parsed_block; 210 241 } … … 222 253 * Applies the custom CSS class name to the block's rendered HTML. 223 254 * 224 * The class name is generated in {@see wp_render_custom_css_support_styles()} 225 * and stored in block attributes. This filter adds it to the actual markup. 226 * 227 * @since 7.0.0 255 * Uses the class name that {@see wp_render_custom_css_support_styles()} 256 * stored in the block attributes. Blocks that a parent parsed and rendered 257 * itself never reach that filter, so the class name is generated here instead 258 * via {@see wp_generate_custom_css_class_name()}. 259 * 260 * @since 7.0.0 261 * @since 7.2.0 Generates the class name when the block attributes lack one. 228 262 * 229 263 * @param string $block_content Rendered block content. … … 232 266 * 233 267 * @phpstan-param array{ 268 * blockName: string|null, 234 269 * attrs: array{ 235 270 * className?: string, 271 * style?: array{ 272 * css?: string, 273 * ... 274 * }, 236 275 * ... 237 276 * }, … … 240 279 */ 241 280 function wp_render_custom_css_class_name( $block_content, $block ) { 242 $class_name_attr = $block['attrs']['className'] ?? null; 243 $class_name_prefix = 'wp-custom-css-'; 244 if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, $class_name_prefix ) ) { 245 return $block_content; 246 } 281 $class_name_attr = $block['attrs']['className'] ?? null; 282 $class_name_prefix = 'wp-custom-css-'; 283 $matched_class_name = null; 247 284 248 285 // Parse out the 'wp-custom-css-*' class name added by wp_render_custom_css_support_styles(). 249 $matched_class_name = null; 250 $token_delimiter = " \t\f\r\n"; 251 $class_token = strtok( $class_name_attr, $token_delimiter ); 252 while ( false !== $class_token ) { 253 if ( str_starts_with( $class_token, $class_name_prefix ) ) { 254 $matched_class_name = $class_token; 255 break; 256 } 257 $class_token = strtok( $token_delimiter ); 258 } 286 if ( is_string( $class_name_attr ) && str_contains( $class_name_attr, $class_name_prefix ) ) { 287 $token_delimiter = " \t\f\r\n"; 288 $class_token = strtok( $class_name_attr, $token_delimiter ); 289 while ( false !== $class_token ) { 290 if ( str_starts_with( $class_token, $class_name_prefix ) ) { 291 $matched_class_name = $class_token; 292 break; 293 } 294 $class_token = strtok( $token_delimiter ); 295 } 296 } 297 298 // Direct WP_Block::render() calls skip render_block_data for the block itself. 299 if ( null === $matched_class_name ) { 300 $matched_class_name = wp_generate_custom_css_class_name( $block ); 301 } 302 259 303 if ( null === $matched_class_name ) { 260 304 return $block_content; -
trunk/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php
r64449 r64676 7 7 */ 8 8 class Tests_Block_Supports_WpRenderCustomCssClassName extends WP_UnitTestCase { 9 /** 10 * @var string|null 11 */ 12 private $test_block_name; 13 14 public function set_up() { 15 parent::set_up(); 16 $this->test_block_name = null; 17 18 global $wp_styles, $wp_script_modules; 19 $wp_styles = null; 20 $wp_script_modules = null; 21 } 22 23 public function tear_down() { 24 if ( $this->test_block_name ) { 25 unregister_block_type( $this->test_block_name ); 26 } 27 $this->test_block_name = null; 28 29 global $wp_styles, $wp_script_modules; 30 $wp_styles = null; 31 $wp_script_modules = null; 32 33 parent::tear_down(); 34 } 9 35 10 36 /** … … 160 186 ); 161 187 } 188 189 /** 190 * Returns the custom CSS class name found in a string of rendered markup. 191 * 192 * @param string $markup Rendered block markup. 193 * @return string|null The custom CSS class name, or null if there is none. 194 */ 195 private function get_custom_css_class_name_from_markup( $markup ) { 196 return preg_match( '/\bwp-custom-css-[0-9a-f]+\b/', $markup, $matches ) ? $matches[0] : null; 197 } 198 199 /** 200 * Returns the custom CSS rules currently attached to the custom CSS stylesheet. 201 * 202 * @return string The inline custom CSS. 203 */ 204 private function get_enqueued_custom_css() { 205 $after = wp_styles()->get_data( 'wp-block-custom-css', 'after' ); 206 207 return is_array( $after ) ? implode( "\n", $after ) : ''; 208 } 209 210 /** 211 * Tests that the class name and stylesheet are still generated for blocks that 212 * are rendered without the `render_block_data` filter running. 213 * 214 * Direct `WP_Block::render()` calls skip `render_block_data` for the block 215 * itself, as when `core/navigation` renders a `wp_navigation` post's blocks. 216 * 217 * @ticket 66345 218 * 219 * @covers ::wp_render_custom_css_class_name 220 */ 221 public function test_generates_class_when_render_block_data_skipped() { 222 $this->test_block_name = 'test/custom-css-skipped-block-data'; 223 register_block_type( 224 $this->test_block_name, 225 array( 226 'api_version' => 3, 227 'attributes' => array( 228 'style' => array( 229 'type' => 'object', 230 ), 231 ), 232 'supports' => array( 'customCSS' => true ), 233 ) 234 ); 235 236 $block = array( 237 'blockName' => 'test/custom-css-skipped-block-data', 238 'attrs' => array( 239 'style' => array( 240 'css' => 'padding: 0;', 241 ), 242 ), 243 ); 244 245 $result = wp_render_custom_css_class_name( '<li class="wp-block-navigation-item">Home</li>', $block ); 246 $class_name = $this->get_custom_css_class_name_from_markup( $result ); 247 248 $this->assertNotNull( $class_name, 'A custom CSS class should be added even though render_block_data did not run.' ); 249 $this->assertStringContainsString( 'has-custom-css', $result, 'The has-custom-css class should be added.' ); 250 $this->assertStringContainsString( 'wp-block-navigation-item', $result, 'Existing classes should be preserved.' ); 251 $this->assertStringContainsString( 252 '.' . $class_name, 253 $this->get_enqueued_custom_css(), 254 'The custom CSS stylesheet should contain a rule for the generated class.' 255 ); 256 } 257 258 /** 259 * Tests that a block is not given a second class name when `render_block_data` 260 * has already generated one. 261 * 262 * @ticket 66345 263 * 264 * @covers ::wp_render_custom_css_class_name 265 */ 266 public function test_does_not_regenerate_existing_class() { 267 $this->test_block_name = 'test/custom-css-existing-class'; 268 register_block_type( 269 $this->test_block_name, 270 array( 271 'api_version' => 3, 272 'attributes' => array( 273 'style' => array( 274 'type' => 'object', 275 ), 276 ), 277 'supports' => array( 'customCSS' => true ), 278 ) 279 ); 280 281 $parsed_block = array( 282 'blockName' => 'test/custom-css-existing-class', 283 'attrs' => array( 284 'style' => array( 285 'css' => 'padding: 0;', 286 ), 287 ), 288 ); 289 290 $filtered = wp_render_custom_css_support_styles( $parsed_block ); 291 $result = wp_render_custom_css_class_name( '<p>Test content</p>', $filtered ); 292 293 $this->assertSame( 294 1, 295 preg_match_all( '/wp-custom-css-[0-9a-f]+/', $result ), 296 'Only the class name generated by render_block_data should be added.' 297 ); 298 $this->assertStringContainsString( 299 $filtered['attrs']['className'], 300 $result, 301 'The class name generated by render_block_data should be the one added to the markup.' 302 ); 303 } 304 305 /** 306 * Tests that blocks inside a navigation menu get their custom CSS on the front end. 307 * 308 * @ticket 66345 309 * 310 * @covers ::wp_render_custom_css_class_name 311 */ 312 public function test_custom_css_is_rendered_for_blocks_inside_a_navigation_menu() { 313 /* 314 * Custom CSS is stripped on save for users without the `edit_css` capability, 315 * which the default test user does not have. Hooks are restored on tear down. 316 */ 317 remove_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); 318 319 $navigation_id = self::factory()->post->create( 320 array( 321 'post_type' => 'wp_navigation', 322 'post_title' => 'Custom CSS Menu', 323 'post_status' => 'publish', 324 'post_content' => '<!-- wp:navigation-link {"label":"Home","url":"/","kind":"custom","isTopLevelLink":true,"style":{"css":"padding: 0;"}} /-->', 325 ) 326 ); 327 328 $rendered = do_blocks( '<!-- wp:navigation {"ref":' . $navigation_id . '} /-->' ); 329 330 $class_name = $this->get_custom_css_class_name_from_markup( $rendered ); 331 332 $this->assertNotNull( $class_name, 'The navigation link should be given a custom CSS class.' ); 333 $this->assertStringContainsString( 334 '.' . $class_name, 335 $this->get_enqueued_custom_css(), 336 'The custom CSS stylesheet should contain a rule for the navigation link.' 337 ); 338 } 162 339 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)