From 6ae5afcfaf27ef832abf10665ef9b19731f62ac7 Mon Sep 17 00:00:00 2001 From: David Baumwald Date: Tue, 7 Feb 2023 18:09:44 +0000 Subject: [PATCH] Editor: `svn add` new files missed in [55285]. Follow-up to [55285]. See #57618. git-svn-id: https://develop.svn.wordpress.org/trunk@55286 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/block-supports/position.php | 151 ++++++++++++++ .../wpRenderPositionSupport.php | 186 ++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 src/wp-includes/block-supports/position.php create mode 100644 tests/phpunit/tests/block-supports/wpRenderPositionSupport.php diff --git a/src/wp-includes/block-supports/position.php b/src/wp-includes/block-supports/position.php new file mode 100644 index 0000000000..8dd5f662e3 --- /dev/null +++ b/src/wp-includes/block-supports/position.php @@ -0,0 +1,151 @@ +attributes ) { + $block_type->attributes = array(); + } + + if ( $has_position_support && ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } +} + +/** + * Renders position styles to the block wrapper. + * + * @since 6.2.0 + * @access private + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function wp_render_position_support( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $has_position_support = block_has_support( $block_type, array( 'position' ), false ); + + if ( + ! $has_position_support || + empty( $block['attrs']['style']['position'] ) + ) { + return $block_content; + } + + $global_settings = wp_get_global_settings(); + $theme_has_sticky_support = _wp_array_get( $global_settings, array( 'position', 'sticky' ), false ); + $theme_has_fixed_support = _wp_array_get( $global_settings, array( 'position', 'fixed' ), false ); + + // Only allow output for position types that the theme supports. + $allowed_position_types = array(); + if ( true === $theme_has_sticky_support ) { + $allowed_position_types[] = 'sticky'; + } + if ( true === $theme_has_fixed_support ) { + $allowed_position_types[] = 'fixed'; + } + + $style_attribute = _wp_array_get( $block, array( 'attrs', 'style' ), null ); + $class_name = wp_unique_id( 'wp-container-' ); + $selector = ".$class_name"; + $position_styles = array(); + $position_type = _wp_array_get( $style_attribute, array( 'position', 'type' ), '' ); + $wrapper_classes = array(); + + if ( + in_array( $position_type, $allowed_position_types, true ) + ) { + $wrapper_classes[] = $class_name; + $wrapper_classes[] = 'is-position-' . $position_type; + $sides = array( 'top', 'right', 'bottom', 'left' ); + + foreach ( $sides as $side ) { + $side_value = _wp_array_get( $style_attribute, array( 'position', $side ) ); + if ( null !== $side_value ) { + /* + * For fixed or sticky top positions, + * ensure the value includes an offset for the logged in admin bar. + */ + if ( + 'top' === $side && + ( 'fixed' === $position_type || 'sticky' === $position_type ) + ) { + // Ensure 0 values can be used in `calc()` calculations. + if ( '0' === $side_value || 0 === $side_value ) { + $side_value = '0px'; + } + + // Ensure current side value also factors in the height of the logged in admin bar. + $side_value = "calc($side_value + var(--wp-admin--admin-bar--position-offset, 0px))"; + } + + $position_styles[] = + array( + 'selector' => $selector, + 'declarations' => array( + $side => $side_value, + ), + ); + } + } + + $position_styles[] = + array( + 'selector' => $selector, + 'declarations' => array( + 'position' => $position_type, + 'z-index' => '10', + ), + ); + } + + if ( ! empty( $position_styles ) ) { + /* + * Add to the style engine store to enqueue and render position styles. + */ + wp_style_engine_get_stylesheet_from_css_rules( + $position_styles, + array( + 'context' => 'block-supports', + 'prettify' => false, + ) + ); + + // Inject class name to block container markup. + $content = new WP_HTML_Tag_Processor( $block_content ); + $content->next_tag(); + foreach ( $wrapper_classes as $class ) { + $content->add_class( $class ); + } + return (string) $content; + } + + return $block_content; +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'position', + array( + 'register_attribute' => 'wp_register_position_support', + ) +); +add_filter( 'render_block', 'wp_render_position_support', 10, 2 ); diff --git a/tests/phpunit/tests/block-supports/wpRenderPositionSupport.php b/tests/phpunit/tests/block-supports/wpRenderPositionSupport.php new file mode 100644 index 0000000000..4aa1418fe5 --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpRenderPositionSupport.php @@ -0,0 +1,186 @@ +test_block_name = null; + $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + WP_Style_Engine_CSS_Rules_Store::remove_all_stores(); + } + + public function tear_down() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + + // Clear up the filters to modify the theme root. + remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + WP_Style_Engine_CSS_Rules_Store::remove_all_stores(); + unregister_block_type( $this->test_block_name ); + $this->test_block_name = null; + parent::tear_down(); + } + + public function filter_set_theme_root() { + return $this->theme_root; + } + + /** + * Tests that position block support works as expected. + * + * @ticket 57618 + * + * @covers ::wp_render_position_support + * + * @dataProvider data_position_block_support + * + * @param string $theme_name The theme to switch to. + * @param string $block_name The test block name to register. + * @param mixed $position_settings The position block support settings. + * @param mixed $position_style The position styles within the block attributes. + * @param string $expected_wrapper Expected markup for the block wrapper. + * @param string $expected_styles Expected styles enqueued by the style engine. + */ + public function test_position_block_support( $theme_name, $block_name, $position_settings, $position_style, $expected_wrapper, $expected_styles ) { + switch_theme( $theme_name ); + $this->test_block_name = $block_name; + + register_block_type( + $this->test_block_name, + array( + 'api_version' => 2, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'position' => $position_settings, + ), + ) + ); + + $block = array( + 'blockName' => 'test/position-rules-are-output', + 'attrs' => array( + 'style' => array( + 'position' => $position_style, + ), + ), + ); + + $actual = wp_render_position_support( '
Content
', $block ); + + $this->assertMatchesRegularExpression( + $expected_wrapper, + $actual, + 'Position block wrapper markup should be correct' + ); + + $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( + 'block-supports', + array( + 'prettify' => false, + ) + ); + + $this->assertMatchesRegularExpression( + $expected_styles, + $actual_stylesheet, + 'Position style rules output should be correct' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_position_block_support() { + return array( + 'sticky position style is applied' => array( + 'theme_name' => 'block-theme-child-with-fluid-typography', + 'block_name' => 'test/position-rules-are-output', + 'position_settings' => true, + 'position_style' => array( + 'type' => 'sticky', + 'top' => '0px', + ), + 'expected_wrapper' => '/^
Content<\/div>$/', + 'expected_styles' => '/^.wp-container-\d+' . preg_quote( '{top:calc(0px + var(--wp-admin--admin-bar--position-offset, 0px));position:sticky;z-index:10;}' ) . '$/', + ), + 'sticky position style is not applied if theme does not support it' => array( + 'theme_name' => 'default', + 'block_name' => 'test/position-rules-without-theme-support', + 'position_settings' => true, + 'position_style' => array( + 'type' => 'sticky', + 'top' => '0px', + ), + 'expected_wrapper' => '/^
Content<\/div>$/', + 'expected_styles' => '/^$/', + ), + 'sticky position style is not applied if block does not support it' => array( + 'theme_name' => 'block-theme-child-with-fluid-typography', + 'block_name' => 'test/position-rules-without-block-support', + 'position_settings' => false, + 'position_style' => array( + 'type' => 'sticky', + 'top' => '0px', + ), + 'expected_wrapper' => '/^
Content<\/div>$/', + 'expected_styles' => '/^$/', + ), + 'sticky position style is not applied if type is not valid' => array( + 'theme_name' => 'block-theme-child-with-fluid-typography', + 'block_name' => 'test/position-rules-with-valid-type', + 'position_settings' => true, + 'position_style' => array( + 'type' => 'illegal-type', + 'top' => '0px', + ), + 'expected_wrapper' => '/^
Content<\/div>$/', + 'expected_styles' => '/^$/', + ), + ); + } +}