[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Layout block support flag. 4 * 5 * @package WordPress 6 * @since 5.8.0 7 */ 8 9 /** 10 * Returns layout definitions, keyed by layout type. 11 * 12 * Provides a common definition of slugs, classnames, base styles, and spacing styles for each layout type. 13 * When making changes or additions to layout definitions, the corresponding JavaScript definitions should 14 * also be updated. 15 * 16 * @since 6.3.0 17 * @access private 18 * 19 * @return array[] Layout definitions. 20 */ 21 function wp_get_layout_definitions() { 22 $layout_definitions = array( 23 'default' => array( 24 'name' => 'default', 25 'slug' => 'flow', 26 'className' => 'is-layout-flow', 27 'baseStyles' => array( 28 array( 29 'selector' => ' > .alignleft', 30 'rules' => array( 31 'float' => 'left', 32 'margin-inline-start' => '0', 33 'margin-inline-end' => '2em', 34 ), 35 ), 36 array( 37 'selector' => ' > .alignright', 38 'rules' => array( 39 'float' => 'right', 40 'margin-inline-start' => '2em', 41 'margin-inline-end' => '0', 42 ), 43 ), 44 array( 45 'selector' => ' > .aligncenter', 46 'rules' => array( 47 'margin-left' => 'auto !important', 48 'margin-right' => 'auto !important', 49 ), 50 ), 51 ), 52 'spacingStyles' => array( 53 array( 54 'selector' => ' > :first-child:first-child', 55 'rules' => array( 56 'margin-block-start' => '0', 57 ), 58 ), 59 array( 60 'selector' => ' > :last-child:last-child', 61 'rules' => array( 62 'margin-block-end' => '0', 63 ), 64 ), 65 array( 66 'selector' => ' > *', 67 'rules' => array( 68 'margin-block-start' => null, 69 'margin-block-end' => '0', 70 ), 71 ), 72 ), 73 ), 74 'constrained' => array( 75 'name' => 'constrained', 76 'slug' => 'constrained', 77 'className' => 'is-layout-constrained', 78 'baseStyles' => array( 79 array( 80 'selector' => ' > .alignleft', 81 'rules' => array( 82 'float' => 'left', 83 'margin-inline-start' => '0', 84 'margin-inline-end' => '2em', 85 ), 86 ), 87 array( 88 'selector' => ' > .alignright', 89 'rules' => array( 90 'float' => 'right', 91 'margin-inline-start' => '2em', 92 'margin-inline-end' => '0', 93 ), 94 ), 95 array( 96 'selector' => ' > .aligncenter', 97 'rules' => array( 98 'margin-left' => 'auto !important', 99 'margin-right' => 'auto !important', 100 ), 101 ), 102 array( 103 'selector' => ' > :where(:not(.alignleft):not(.alignright):not(.alignfull))', 104 'rules' => array( 105 'max-width' => 'var(--wp--style--global--content-size)', 106 'margin-left' => 'auto !important', 107 'margin-right' => 'auto !important', 108 ), 109 ), 110 array( 111 'selector' => ' > .alignwide', 112 'rules' => array( 113 'max-width' => 'var(--wp--style--global--wide-size)', 114 ), 115 ), 116 ), 117 'spacingStyles' => array( 118 array( 119 'selector' => ' > :first-child:first-child', 120 'rules' => array( 121 'margin-block-start' => '0', 122 ), 123 ), 124 array( 125 'selector' => ' > :last-child:last-child', 126 'rules' => array( 127 'margin-block-end' => '0', 128 ), 129 ), 130 array( 131 'selector' => ' > *', 132 'rules' => array( 133 'margin-block-start' => null, 134 'margin-block-end' => '0', 135 ), 136 ), 137 ), 138 ), 139 'flex' => array( 140 'name' => 'flex', 141 'slug' => 'flex', 142 'className' => 'is-layout-flex', 143 'displayMode' => 'flex', 144 'baseStyles' => array( 145 array( 146 'selector' => '', 147 'rules' => array( 148 'flex-wrap' => 'wrap', 149 'align-items' => 'center', 150 ), 151 ), 152 array( 153 'selector' => ' > *', 154 'rules' => array( 155 'margin' => '0', 156 ), 157 ), 158 ), 159 'spacingStyles' => array( 160 array( 161 'selector' => '', 162 'rules' => array( 163 'gap' => null, 164 ), 165 ), 166 ), 167 ), 168 'grid' => array( 169 'name' => 'grid', 170 'slug' => 'grid', 171 'className' => 'is-layout-grid', 172 'displayMode' => 'grid', 173 'baseStyles' => array( 174 array( 175 'selector' => ' > *', 176 'rules' => array( 177 'margin' => '0', 178 ), 179 ), 180 ), 181 'spacingStyles' => array( 182 array( 183 'selector' => '', 184 'rules' => array( 185 'gap' => null, 186 ), 187 ), 188 ), 189 ), 190 ); 191 192 return $layout_definitions; 193 } 194 195 /** 196 * Registers the layout block attribute for block types that support it. 197 * 198 * @since 5.8.0 199 * @since 6.3.0 Check for layout support via the `layout` key with fallback to `__experimentalLayout`. 200 * @access private 201 * 202 * @param WP_Block_Type $block_type Block Type. 203 */ 204 function wp_register_layout_support( $block_type ) { 205 $support_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false ); 206 if ( $support_layout ) { 207 if ( ! $block_type->attributes ) { 208 $block_type->attributes = array(); 209 } 210 211 if ( ! array_key_exists( 'layout', $block_type->attributes ) ) { 212 $block_type->attributes['layout'] = array( 213 'type' => 'object', 214 ); 215 } 216 } 217 } 218 219 /** 220 * Generates the CSS corresponding to the provided layout. 221 * 222 * @since 5.9.0 223 * @since 6.1.0 Added `$block_spacing` param, use style engine to enqueue styles. 224 * @since 6.3.0 Added grid layout type. 225 * @access private 226 * 227 * @param string $selector CSS selector. 228 * @param array $layout Layout object. The one that is passed has already checked 229 * the existence of default block layout. 230 * @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false. 231 * @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null. 232 * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false. 233 * @param string $fallback_gap_value Optional. The block gap value to apply. Default '0.5em'. 234 * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null. 235 * @return string CSS styles on success. Else, empty string. 236 */ 237 function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em', $block_spacing = null ) { 238 $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; 239 $layout_styles = array(); 240 241 if ( 'default' === $layout_type ) { 242 if ( $has_block_gap_support ) { 243 if ( is_array( $gap_value ) ) { 244 $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null; 245 } 246 if ( null !== $gap_value && ! $should_skip_gap_serialization ) { 247 // Get spacing CSS variable from preset value if provided. 248 if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) { 249 $index_to_splice = strrpos( $gap_value, '|' ) + 1; 250 $slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) ); 251 $gap_value = "var(--wp--preset--spacing--$slug)"; 252 } 253 254 array_push( 255 $layout_styles, 256 array( 257 'selector' => "$selector > *", 258 'declarations' => array( 259 'margin-block-start' => '0', 260 'margin-block-end' => '0', 261 ), 262 ), 263 array( 264 'selector' => "$selector$selector > * + *", 265 'declarations' => array( 266 'margin-block-start' => $gap_value, 267 'margin-block-end' => '0', 268 ), 269 ) 270 ); 271 } 272 } 273 } elseif ( 'constrained' === $layout_type ) { 274 $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : ''; 275 $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : ''; 276 $justify_content = isset( $layout['justifyContent'] ) ? $layout['justifyContent'] : 'center'; 277 278 $all_max_width_value = $content_size ? $content_size : $wide_size; 279 $wide_max_width_value = $wide_size ? $wide_size : $content_size; 280 281 // Make sure there is a single CSS rule, and all tags are stripped for security. 282 $all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] ); 283 $wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] ); 284 285 $margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important'; 286 $margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important'; 287 288 if ( $content_size || $wide_size ) { 289 array_push( 290 $layout_styles, 291 array( 292 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))", 293 'declarations' => array( 294 'max-width' => $all_max_width_value, 295 'margin-left' => $margin_left, 296 'margin-right' => $margin_right, 297 ), 298 ), 299 array( 300 'selector' => "$selector > .alignwide", 301 'declarations' => array( 'max-width' => $wide_max_width_value ), 302 ), 303 array( 304 'selector' => "$selector .alignfull", 305 'declarations' => array( 'max-width' => 'none' ), 306 ) 307 ); 308 309 if ( isset( $block_spacing ) ) { 310 $block_spacing_values = wp_style_engine_get_styles( 311 array( 312 'spacing' => $block_spacing, 313 ) 314 ); 315 316 /* 317 * Handle negative margins for alignfull children of blocks with custom padding set. 318 * They're added separately because padding might only be set on one side. 319 */ 320 if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) { 321 $padding_right = $block_spacing_values['declarations']['padding-right']; 322 $layout_styles[] = array( 323 'selector' => "$selector > .alignfull", 324 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ), 325 ); 326 } 327 if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) { 328 $padding_left = $block_spacing_values['declarations']['padding-left']; 329 $layout_styles[] = array( 330 'selector' => "$selector > .alignfull", 331 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ), 332 ); 333 } 334 } 335 } 336 337 if ( 'left' === $justify_content ) { 338 $layout_styles[] = array( 339 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))", 340 'declarations' => array( 'margin-left' => '0 !important' ), 341 ); 342 } 343 344 if ( 'right' === $justify_content ) { 345 $layout_styles[] = array( 346 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))", 347 'declarations' => array( 'margin-right' => '0 !important' ), 348 ); 349 } 350 351 if ( $has_block_gap_support ) { 352 if ( is_array( $gap_value ) ) { 353 $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null; 354 } 355 if ( null !== $gap_value && ! $should_skip_gap_serialization ) { 356 // Get spacing CSS variable from preset value if provided. 357 if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) { 358 $index_to_splice = strrpos( $gap_value, '|' ) + 1; 359 $slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) ); 360 $gap_value = "var(--wp--preset--spacing--$slug)"; 361 } 362 363 array_push( 364 $layout_styles, 365 array( 366 'selector' => "$selector > *", 367 'declarations' => array( 368 'margin-block-start' => '0', 369 'margin-block-end' => '0', 370 ), 371 ), 372 array( 373 'selector' => "$selector$selector > * + *", 374 'declarations' => array( 375 'margin-block-start' => $gap_value, 376 'margin-block-end' => '0', 377 ), 378 ) 379 ); 380 } 381 } 382 } elseif ( 'flex' === $layout_type ) { 383 $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal'; 384 385 $justify_content_options = array( 386 'left' => 'flex-start', 387 'right' => 'flex-end', 388 'center' => 'center', 389 ); 390 391 $vertical_alignment_options = array( 392 'top' => 'flex-start', 393 'center' => 'center', 394 'bottom' => 'flex-end', 395 ); 396 397 if ( 'horizontal' === $layout_orientation ) { 398 $justify_content_options += array( 'space-between' => 'space-between' ); 399 $vertical_alignment_options += array( 'stretch' => 'stretch' ); 400 } else { 401 $justify_content_options += array( 'stretch' => 'stretch' ); 402 $vertical_alignment_options += array( 'space-between' => 'space-between' ); 403 } 404 405 if ( ! empty( $layout['flexWrap'] ) && 'nowrap' === $layout['flexWrap'] ) { 406 $layout_styles[] = array( 407 'selector' => $selector, 408 'declarations' => array( 'flex-wrap' => 'nowrap' ), 409 ); 410 } 411 412 if ( $has_block_gap_support && isset( $gap_value ) ) { 413 $combined_gap_value = ''; 414 $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' ); 415 416 foreach ( $gap_sides as $gap_side ) { 417 $process_value = $gap_value; 418 if ( is_array( $gap_value ) ) { 419 $process_value = isset( $gap_value[ $gap_side ] ) ? $gap_value[ $gap_side ] : $fallback_gap_value; 420 } 421 // Get spacing CSS variable from preset value if provided. 422 if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) { 423 $index_to_splice = strrpos( $process_value, '|' ) + 1; 424 $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) ); 425 $process_value = "var(--wp--preset--spacing--$slug)"; 426 } 427 $combined_gap_value .= "$process_value "; 428 } 429 $gap_value = trim( $combined_gap_value ); 430 431 if ( null !== $gap_value && ! $should_skip_gap_serialization ) { 432 $layout_styles[] = array( 433 'selector' => $selector, 434 'declarations' => array( 'gap' => $gap_value ), 435 ); 436 } 437 } 438 439 if ( 'horizontal' === $layout_orientation ) { 440 /* 441 * Add this style only if is not empty for backwards compatibility, 442 * since we intend to convert blocks that had flex layout implemented 443 * by custom css. 444 */ 445 if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { 446 $layout_styles[] = array( 447 'selector' => $selector, 448 'declarations' => array( 'justify-content' => $justify_content_options[ $layout['justifyContent'] ] ), 449 ); 450 } 451 452 if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) { 453 $layout_styles[] = array( 454 'selector' => $selector, 455 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ), 456 ); 457 } 458 } else { 459 $layout_styles[] = array( 460 'selector' => $selector, 461 'declarations' => array( 'flex-direction' => 'column' ), 462 ); 463 if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { 464 $layout_styles[] = array( 465 'selector' => $selector, 466 'declarations' => array( 'align-items' => $justify_content_options[ $layout['justifyContent'] ] ), 467 ); 468 } else { 469 $layout_styles[] = array( 470 'selector' => $selector, 471 'declarations' => array( 'align-items' => 'flex-start' ), 472 ); 473 } 474 if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) { 475 $layout_styles[] = array( 476 'selector' => $selector, 477 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ), 478 ); 479 } 480 } 481 } elseif ( 'grid' === $layout_type ) { 482 if ( ! empty( $layout['columnCount'] ) ) { 483 $layout_styles[] = array( 484 'selector' => $selector, 485 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ), 486 ); 487 } else { 488 $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem'; 489 490 $layout_styles[] = array( 491 'selector' => $selector, 492 'declarations' => array( 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))' ), 493 ); 494 } 495 496 if ( $has_block_gap_support && isset( $gap_value ) ) { 497 $combined_gap_value = ''; 498 $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' ); 499 500 foreach ( $gap_sides as $gap_side ) { 501 $process_value = $gap_value; 502 if ( is_array( $gap_value ) ) { 503 $process_value = isset( $gap_value[ $gap_side ] ) ? $gap_value[ $gap_side ] : $fallback_gap_value; 504 } 505 // Get spacing CSS variable from preset value if provided. 506 if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) { 507 $index_to_splice = strrpos( $process_value, '|' ) + 1; 508 $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) ); 509 $process_value = "var(--wp--preset--spacing--$slug)"; 510 } 511 $combined_gap_value .= "$process_value "; 512 } 513 $gap_value = trim( $combined_gap_value ); 514 515 if ( null !== $gap_value && ! $should_skip_gap_serialization ) { 516 $layout_styles[] = array( 517 'selector' => $selector, 518 'declarations' => array( 'gap' => $gap_value ), 519 ); 520 } 521 } 522 } 523 524 if ( ! empty( $layout_styles ) ) { 525 /* 526 * Add to the style engine store to enqueue and render layout styles. 527 * Return compiled layout styles to retain backwards compatibility. 528 * Since https://github.com/WordPress/gutenberg/pull/42452, 529 * wp_enqueue_block_support_styles is no longer called in this block supports file. 530 */ 531 return wp_style_engine_get_stylesheet_from_css_rules( 532 $layout_styles, 533 array( 534 'context' => 'block-supports', 535 'prettify' => false, 536 ) 537 ); 538 } 539 540 return ''; 541 } 542 543 /** 544 * Renders the layout config to the block wrapper. 545 * 546 * @since 5.8.0 547 * @since 6.3.0 Adds compound class to layout wrapper for global spacing styles. 548 * @since 6.3.0 Check for layout support via the `layout` key with fallback to `__experimentalLayout`. 549 * @access private 550 * 551 * @param string $block_content Rendered block content. 552 * @param array $block Block object. 553 * @return string Filtered block content. 554 */ 555 function wp_render_layout_support_flag( $block_content, $block ) { 556 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); 557 $block_supports_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false ); 558 $layout_from_parent = isset( $block['attrs']['style']['layout']['selfStretch'] ) ? $block['attrs']['style']['layout']['selfStretch'] : null; 559 560 if ( ! $block_supports_layout && ! $layout_from_parent ) { 561 return $block_content; 562 } 563 564 $outer_class_names = array(); 565 566 if ( 'fixed' === $layout_from_parent || 'fill' === $layout_from_parent ) { 567 $container_content_class = wp_unique_id( 'wp-container-content-' ); 568 569 $child_layout_styles = array(); 570 571 if ( 'fixed' === $layout_from_parent && isset( $block['attrs']['style']['layout']['flexSize'] ) ) { 572 $child_layout_styles[] = array( 573 'selector' => ".$container_content_class", 574 'declarations' => array( 575 'flex-basis' => $block['attrs']['style']['layout']['flexSize'], 576 'box-sizing' => 'border-box', 577 ), 578 ); 579 } elseif ( 'fill' === $layout_from_parent ) { 580 $child_layout_styles[] = array( 581 'selector' => ".$container_content_class", 582 'declarations' => array( 583 'flex-grow' => '1', 584 ), 585 ); 586 } 587 588 wp_style_engine_get_stylesheet_from_css_rules( 589 $child_layout_styles, 590 array( 591 'context' => 'block-supports', 592 'prettify' => false, 593 ) 594 ); 595 596 $outer_class_names[] = $container_content_class; 597 } 598 599 // Prep the processor for modifying the block output. 600 $processor = new WP_HTML_Tag_Processor( $block_content ); 601 602 // Having no tags implies there are no tags onto which to add class names. 603 if ( ! $processor->next_tag() ) { 604 return $block_content; 605 } 606 607 /* 608 * A block may not support layout but still be affected by a parent block's layout. 609 * 610 * In these cases add the appropriate class names and then return early; there's 611 * no need to investigate on this block whether additional layout constraints apply. 612 */ 613 if ( ! $block_supports_layout && ! empty( $outer_class_names ) ) { 614 foreach ( $outer_class_names as $class_name ) { 615 $processor->add_class( $class_name ); 616 } 617 return $processor->get_updated_html(); 618 } elseif ( ! $block_supports_layout ) { 619 // Ensure layout classnames are not injected if there is no layout support. 620 return $block_content; 621 } 622 623 $global_settings = wp_get_global_settings(); 624 $fallback_layout = isset( $block_type->supports['layout']['default'] ) 625 ? $block_type->supports['layout']['default'] 626 : array(); 627 if ( empty( $fallback_layout ) ) { 628 $fallback_layout = isset( $block_type->supports['__experimentalLayout']['default'] ) 629 ? $block_type->supports['__experimentalLayout']['default'] 630 : array(); 631 } 632 $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $fallback_layout; 633 634 $class_names = array(); 635 $layout_definitions = wp_get_layout_definitions(); 636 637 /* 638 * Uses an incremental ID that is independent per prefix to make sure that 639 * rendering different numbers of blocks doesn't affect the IDs of other 640 * blocks. Makes the CSS class names stable across paginations 641 * for features like the enhanced pagination of the Query block. 642 */ 643 $container_class = wp_unique_prefixed_id( 644 'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-' 645 ); 646 647 // Set the correct layout type for blocks using legacy content width. 648 if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) { 649 $used_layout['type'] = 'constrained'; 650 } 651 652 $root_padding_aware_alignments = isset( $global_settings['useRootPaddingAwareAlignments'] ) 653 ? $global_settings['useRootPaddingAwareAlignments'] 654 : false; 655 656 if ( 657 $root_padding_aware_alignments && 658 isset( $used_layout['type'] ) && 659 'constrained' === $used_layout['type'] 660 ) { 661 $class_names[] = 'has-global-padding'; 662 } 663 664 /* 665 * The following section was added to reintroduce a small set of layout classnames that were 666 * removed in the 5.9 release (https://github.com/WordPress/gutenberg/issues/38719). It is 667 * not intended to provide an extended set of classes to match all block layout attributes 668 * here. 669 */ 670 if ( ! empty( $block['attrs']['layout']['orientation'] ) ) { 671 $class_names[] = 'is-' . sanitize_title( $block['attrs']['layout']['orientation'] ); 672 } 673 674 if ( ! empty( $block['attrs']['layout']['justifyContent'] ) ) { 675 $class_names[] = 'is-content-justification-' . sanitize_title( $block['attrs']['layout']['justifyContent'] ); 676 } 677 678 if ( ! empty( $block['attrs']['layout']['flexWrap'] ) && 'nowrap' === $block['attrs']['layout']['flexWrap'] ) { 679 $class_names[] = 'is-nowrap'; 680 } 681 682 // Get classname for layout type. 683 if ( isset( $used_layout['type'] ) ) { 684 $layout_classname = isset( $layout_definitions[ $used_layout['type'] ]['className'] ) 685 ? $layout_definitions[ $used_layout['type'] ]['className'] 686 : ''; 687 } else { 688 $layout_classname = isset( $layout_definitions['default']['className'] ) 689 ? $layout_definitions['default']['className'] 690 : ''; 691 } 692 693 if ( $layout_classname && is_string( $layout_classname ) ) { 694 $class_names[] = sanitize_title( $layout_classname ); 695 } 696 697 /* 698 * Only generate Layout styles if the theme has not opted-out. 699 * Attribute-based Layout classnames are output in all cases. 700 */ 701 if ( ! current_theme_supports( 'disable-layout-styles' ) ) { 702 703 $gap_value = isset( $block['attrs']['style']['spacing']['blockGap'] ) 704 ? $block['attrs']['style']['spacing']['blockGap'] 705 : null; 706 /* 707 * Skip if gap value contains unsupported characters. 708 * Regex for CSS value borrowed from `safecss_filter_attr`, and used here 709 * to only match against the value, not the CSS attribute. 710 */ 711 if ( is_array( $gap_value ) ) { 712 foreach ( $gap_value as $key => $value ) { 713 $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value; 714 } 715 } else { 716 $gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value; 717 } 718 719 $fallback_gap_value = isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) 720 ? $block_type->supports['spacing']['blockGap']['__experimentalDefault'] 721 : '0.5em'; 722 $block_spacing = isset( $block['attrs']['style']['spacing'] ) 723 ? $block['attrs']['style']['spacing'] 724 : null; 725 726 /* 727 * If a block's block.json skips serialization for spacing or spacing.blockGap, 728 * don't apply the user-defined value to the styles. 729 */ 730 $should_skip_gap_serialization = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' ); 731 732 $block_gap = isset( $global_settings['spacing']['blockGap'] ) 733 ? $global_settings['spacing']['blockGap'] 734 : null; 735 $has_block_gap_support = isset( $block_gap ); 736 737 $style = wp_get_layout_style( 738 ".$container_class.$container_class", 739 $used_layout, 740 $has_block_gap_support, 741 $gap_value, 742 $should_skip_gap_serialization, 743 $fallback_gap_value, 744 $block_spacing 745 ); 746 747 // Only add container class and enqueue block support styles if unique styles were generated. 748 if ( ! empty( $style ) ) { 749 $class_names[] = $container_class; 750 } 751 } 752 753 // Add combined layout and block classname for global styles to hook onto. 754 $block_name = explode( '/', $block['blockName'] ); 755 $class_names[] = 'wp-block-' . end( $block_name ) . '-' . $layout_classname; 756 757 // Add classes to the outermost HTML tag if necessary. 758 if ( ! empty( $outer_class_names ) ) { 759 foreach ( $outer_class_names as $outer_class_name ) { 760 $processor->add_class( $outer_class_name ); 761 } 762 } 763 764 /** 765 * Attempts to refer to the inner-block wrapping element by its class attribute. 766 * 767 * When examining a block's inner content, if a block has inner blocks, then 768 * the first content item will likely be a text (HTML) chunk immediately 769 * preceding the inner blocks. The last HTML tag in that chunk would then be 770 * an opening tag for an element that wraps the inner blocks. 771 * 772 * There's no reliable way to associate this wrapper in $block_content because 773 * it may have changed during the rendering pipeline (as inner contents is 774 * provided before rendering) and through previous filters. In many cases, 775 * however, the `class` attribute will be a good-enough identifier, so this 776 * code finds the last tag in that chunk and stores the `class` attribute 777 * so that it can be used later when working through the rendered block output 778 * to identify the wrapping element and add the remaining class names to it. 779 * 780 * It's also possible that no inner block wrapper even exists. If that's the 781 * case this code could apply the class names to an invalid element. 782 * 783 * Example: 784 * 785 * $block['innerBlocks'] = array( $list_item ); 786 * $block['innerContent'] = array( '<ul class="list-wrapper is-unordered">', null, '</ul>' ); 787 * 788 * // After rendering, the initial contents may have been modified by other renderers or filters. 789 * $block_content = <<<HTML 790 * <figure> 791 * <ul class="annotated-list list-wrapper is-unordered"> 792 * <li>Code</li> 793 * </ul><figcaption>It's a list!</figcaption> 794 * </figure> 795 * HTML; 796 * 797 * Although it is possible that the original block-wrapper classes are changed in $block_content 798 * from how they appear in $block['innerContent'], it's likely that the original class attributes 799 * are still present in the wrapper as they are in this example. Frequently, additional classes 800 * will also be present; rarely should classes be removed. 801 * 802 * @todo Find a better way to match the first inner block. If it's possible to identify where the 803 * first inner block starts, then it will be possible to find the last tag before it starts 804 * and then that tag, if an opening tag, can be solidly identified as a wrapping element. 805 * Can some unique value or class or ID be added to the inner blocks when they process 806 * so that they can be extracted here safely without guessing? Can the block rendering function 807 * return information about where the rendered inner blocks start? 808 * 809 * @var string|null 810 */ 811 $inner_block_wrapper_classes = null; 812 $first_chunk = isset( $block['innerContent'][0] ) ? $block['innerContent'][0] : null; 813 if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) { 814 $first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk ); 815 while ( $first_chunk_processor->next_tag() ) { 816 $class_attribute = $first_chunk_processor->get_attribute( 'class' ); 817 if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) { 818 $inner_block_wrapper_classes = $class_attribute; 819 } 820 } 821 } 822 823 /* 824 * If necessary, advance to what is likely to be an inner block wrapper tag. 825 * 826 * This advances until it finds the first tag containing the original class 827 * attribute from above. If none is found it will scan to the end of the block 828 * and fail to add any class names. 829 * 830 * If there is no block wrapper it won't advance at all, in which case the 831 * class names will be added to the first and outermost tag of the block. 832 * For cases where this outermost tag is the only tag surrounding inner 833 * blocks then the outer wrapper and inner wrapper are the same. 834 */ 835 do { 836 if ( ! $inner_block_wrapper_classes ) { 837 break; 838 } 839 840 $class_attribute = $processor->get_attribute( 'class' ); 841 if ( is_string( $class_attribute ) && str_contains( $class_attribute, $inner_block_wrapper_classes ) ) { 842 break; 843 } 844 } while ( $processor->next_tag() ); 845 846 // Add the remaining class names. 847 foreach ( $class_names as $class_name ) { 848 $processor->add_class( $class_name ); 849 } 850 851 return $processor->get_updated_html(); 852 } 853 854 // Register the block support. 855 WP_Block_Supports::get_instance()->register( 856 'layout', 857 array( 858 'register_attribute' => 'wp_register_layout_support', 859 ) 860 ); 861 add_filter( 'render_block', 'wp_render_layout_support_flag', 10, 2 ); 862 863 /** 864 * For themes without theme.json file, make sure 865 * to restore the inner div for the group block 866 * to avoid breaking styles relying on that div. 867 * 868 * @since 5.8.0 869 * @access private 870 * 871 * @param string $block_content Rendered block content. 872 * @param array $block Block object. 873 * @return string Filtered block content. 874 */ 875 function wp_restore_group_inner_container( $block_content, $block ) { 876 $tag_name = isset( $block['attrs']['tagName'] ) ? $block['attrs']['tagName'] : 'div'; 877 $group_with_inner_container_regex = sprintf( 878 '/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U', 879 preg_quote( $tag_name, '/' ) 880 ); 881 882 if ( 883 wp_theme_has_theme_json() || 884 1 === preg_match( $group_with_inner_container_regex, $block_content ) || 885 ( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] ) 886 ) { 887 return $block_content; 888 } 889 890 /* 891 * This filter runs after the layout classnames have been added to the block, so they 892 * have to be removed from the outer wrapper and then added to the inner. 893 */ 894 $layout_classes = array(); 895 $processor = new WP_HTML_Tag_Processor( $block_content ); 896 897 if ( $processor->next_tag( array( 'class_name' => 'wp-block-group' ) ) ) { 898 foreach ( $processor->class_list() as $class_name ) { 899 if ( str_contains( $class_name, 'is-layout-' ) ) { 900 $layout_classes[] = $class_name; 901 $processor->remove_class( $class_name ); 902 } 903 } 904 } 905 906 $content_without_layout_classes = $processor->get_updated_html(); 907 $replace_regex = sprintf( 908 '/(^\s*<%1$s\b[^>]*wp-block-group[^>]*>)(.*)(<\/%1$s>\s*$)/ms', 909 preg_quote( $tag_name, '/' ) 910 ); 911 $updated_content = preg_replace_callback( 912 $replace_regex, 913 static function ( $matches ) { 914 return $matches[1] . '<div class="wp-block-group__inner-container">' . $matches[2] . '</div>' . $matches[3]; 915 }, 916 $content_without_layout_classes 917 ); 918 919 // Add layout classes to inner wrapper. 920 if ( ! empty( $layout_classes ) ) { 921 $processor = new WP_HTML_Tag_Processor( $updated_content ); 922 if ( $processor->next_tag( array( 'class_name' => 'wp-block-group__inner-container' ) ) ) { 923 foreach ( $layout_classes as $class_name ) { 924 $processor->add_class( $class_name ); 925 } 926 } 927 $updated_content = $processor->get_updated_html(); 928 } 929 return $updated_content; 930 } 931 932 add_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10, 2 ); 933 934 /** 935 * For themes without theme.json file, make sure 936 * to restore the outer div for the aligned image block 937 * to avoid breaking styles relying on that div. 938 * 939 * @since 6.0.0 940 * @access private 941 * 942 * @param string $block_content Rendered block content. 943 * @param array $block Block object. 944 * @return string Filtered block content. 945 */ 946 function wp_restore_image_outer_container( $block_content, $block ) { 947 $image_with_align = " 948 /# 1) everything up to the class attribute contents 949 ( 950 ^\s* 951 <figure\b 952 [^>]* 953 \bclass= 954 [\"'] 955 ) 956 # 2) the class attribute contents 957 ( 958 [^\"']* 959 \bwp-block-image\b 960 [^\"']* 961 \b(?:alignleft|alignright|aligncenter)\b 962 [^\"']* 963 ) 964 # 3) everything after the class attribute contents 965 ( 966 [\"'] 967 [^>]* 968 > 969 .* 970 <\/figure> 971 )/iUx"; 972 973 if ( 974 wp_theme_has_theme_json() || 975 0 === preg_match( $image_with_align, $block_content, $matches ) 976 ) { 977 return $block_content; 978 } 979 980 $wrapper_classnames = array( 'wp-block-image' ); 981 982 // If the block has a classNames attribute these classnames need to be removed from the content and added back 983 // to the new wrapper div also. 984 if ( ! empty( $block['attrs']['className'] ) ) { 985 $wrapper_classnames = array_merge( $wrapper_classnames, explode( ' ', $block['attrs']['className'] ) ); 986 } 987 $content_classnames = explode( ' ', $matches[2] ); 988 $filtered_content_classnames = array_diff( $content_classnames, $wrapper_classnames ); 989 990 return '<div class="' . implode( ' ', $wrapper_classnames ) . '">' . $matches[1] . implode( ' ', $filtered_content_classnames ) . $matches[3] . '</div>'; 991 } 992 993 add_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10, 2 );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Apr 20 08:20:01 2024 | Cross-referenced by PHPXref |