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