From 52e5549f9af2ec14a21afc186b77d50167be5ba7 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Mon, 30 May 2022 12:12:59 +1000 Subject: [PATCH 1/7] Initial commit --- .../class-wp-style-engine-store.php | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 packages/style-engine/class-wp-style-engine-store.php diff --git a/packages/style-engine/class-wp-style-engine-store.php b/packages/style-engine/class-wp-style-engine-store.php new file mode 100644 index 00000000000000..7a6fb90173524d --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-store.php @@ -0,0 +1,140 @@ +registered_styles[ $key ] ) ) { + $style_data = array_unique( array_merge( $this->registered_styles[ $key ], $style_data ) ); + } + $this->registered_styles[ $key ] = $style_data; + } + + /** + * Retrieves style data from the store. + * + * @param string $key Optional unique key for a $style_data object to return a single style object. + * @param array? $style_data Associative array of style information. + */ + public function get( $key = null ) { + if ( isset( $this->registered_styles[ $key ] ) ) { + return $this->registered_styles[ $key ]; + } + return $this->registered_styles; + } +} + +/* + +For each style category we could have a separate object: +e.g., + +$global_style_store = new WP_Style_Engine_Store(); +$block_supports_style_store = new WP_Style_Engine_Store(); + + +@TODO + +Work out enqueuing and rendering +*/ + /** + * Prints registered styles in the page head or footer. + * + * @see $this->enqueue_block_support_styles + + public function output_registered_block_support_styles() { + if ( empty( $this->registered_block_support_styles ) ) { + return; + } + + $output = ''; + + foreach ( $this->registered_block_support_styles as $selector => $rules ) { + $output .= "\t$selector { "; + $output .= implode( ' ', $rules ); + $output .= " }\n"; + } + + echo "\n"; + } */ + + /** + * Taken from gutenberg_enqueue_block_support_styles() + * + * This function takes care of adding inline styles + * in the proper place, depending on the theme in use. + * + * For block themes, it's loaded in the head. + * For classic ones, it's loaded in the body + * because the wp_head action happens before + * the render_block. + * + * @see gutenberg_enqueue_block_support_styles() + + private function enqueue_block_support_styles() { + $action_hook_name = 'wp_footer'; + if ( wp_is_block_theme() ) { + $action_hook_name = 'wp_head'; + } + add_action( + $action_hook_name, + array( $this, 'output_registered_block_support_styles' ) + ); + } */ + + From 8c141e25357605f55a014c612f38580baa2e24a4 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Tue, 31 May 2022 14:51:10 +1000 Subject: [PATCH 2/7] Toying around with a style engine store mainly. --- .../class-wp-style-engine-renderer.php | 78 ++++++++++++ .../class-wp-style-engine-store.php | 114 +++++------------- .../style-engine/class-wp-style-engine.php | 45 +++++++ 3 files changed, 151 insertions(+), 86 deletions(-) create mode 100644 packages/style-engine/class-wp-style-engine-renderer.php diff --git a/packages/style-engine/class-wp-style-engine-renderer.php b/packages/style-engine/class-wp-style-engine-renderer.php new file mode 100644 index 00000000000000..fb9d5885f228a7 --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-renderer.php @@ -0,0 +1,78 @@ +enqueue_block_support_styles + */ + public function render_registered_styles( $styles ) { + if ( empty( $styles ) ) { + return; + } + + $output = ''; + + foreach ( $styles as $selector => $rules ) { + $output .= "\t$selector { "; + $output .= implode( ' ', $rules ); + $output .= " }\n"; + } + + echo "\n"; + } + + /** + * Taken from gutenberg_enqueue_block_support_styles() + * + * This function takes care of adding inline styles + * in the proper place, depending on the theme in use. + * + * For block themes, it's loaded in the head. + * For classic ones, it's loaded in the body + * because the wp_head action happens before + * the render_block. + * + * @see gutenberg_enqueue_block_support_styles() + */ + private function enqueue_block_support_styles() { + $action_hook_name = 'wp_footer'; + if ( wp_is_block_theme() ) { + $action_hook_name = 'wp_head'; + } + add_action( + $action_hook_name, + array( $this, 'render_registered_styles' ) + ); + } +} + diff --git a/packages/style-engine/class-wp-style-engine-store.php b/packages/style-engine/class-wp-style-engine-store.php index 7a6fb90173524d..df0d1d1b657968 100644 --- a/packages/style-engine/class-wp-style-engine-store.php +++ b/packages/style-engine/class-wp-style-engine-store.php @@ -7,39 +7,20 @@ * @package Gutenberg */ -// Probably don't need this, but it was just helped to design things. -if ( ! interface_exists( 'WP_Style_Engine_Store_Interface' ) ) { - /** - * Registers and retrieves stored styles. - * - * @access private - */ - interface WP_Style_Engine_Store_Interface { - /** - * Register a style - * - * @param string $key Unique key for a $style_data object. - * @param array $style_data Associative array of style information. - * @return void - */ - public function register( $key, $style_data ); - - /** - * Retrieves style data from the store. - * - * @param string $key Unique key for a $style_data object. - * @return void - */ - public function get( $key ); - } +if ( class_exists( 'WP_Style_Engine_Store' ) ) { + return; } /** * Registers and stores styles to be processed or rendered on the frontend. * + * For each style category we could have a separate object, e.g., + * $global_style_store = new WP_Style_Engine_Store(); + * $block_supports_style_store = new WP_Style_Engine_Store(); + * * @access private */ -class WP_Style_Engine_Store implements WP_Style_Engine_Store_Interface { +class WP_Style_Engine_Store { /** * Registered styles. * @@ -47,6 +28,25 @@ class WP_Style_Engine_Store implements WP_Style_Engine_Store_Interface { */ private $registered_styles = array(); + /** + * A key to identify the store. Default value is 'global'. + * + * @var WP_Style_Engine_Store|null + */ + private $store_key = 'global-styles'; + + /** + * Constructor. + * + * @param string $store_key A key/name/id to identify the store. + * @return void + */ + public function __construct( $store_key = null ) { + if ( ! empty( $store_key ) ) { + $this->store_key = $store_key; + } + } + /** * Register a style * @@ -70,6 +70,8 @@ public function register( $key, $style_data ) { * * @param string $key Optional unique key for a $style_data object to return a single style object. * @param array? $style_data Associative array of style information. + * + * @return array Registered styles */ public function get( $key = null ) { if ( isset( $this->registered_styles[ $key ] ) ) { @@ -78,63 +80,3 @@ public function get( $key = null ) { return $this->registered_styles; } } - -/* - -For each style category we could have a separate object: -e.g., - -$global_style_store = new WP_Style_Engine_Store(); -$block_supports_style_store = new WP_Style_Engine_Store(); - - -@TODO - -Work out enqueuing and rendering -*/ - /** - * Prints registered styles in the page head or footer. - * - * @see $this->enqueue_block_support_styles - - public function output_registered_block_support_styles() { - if ( empty( $this->registered_block_support_styles ) ) { - return; - } - - $output = ''; - - foreach ( $this->registered_block_support_styles as $selector => $rules ) { - $output .= "\t$selector { "; - $output .= implode( ' ', $rules ); - $output .= " }\n"; - } - - echo "\n"; - } */ - - /** - * Taken from gutenberg_enqueue_block_support_styles() - * - * This function takes care of adding inline styles - * in the proper place, depending on the theme in use. - * - * For block themes, it's loaded in the head. - * For classic ones, it's loaded in the body - * because the wp_head action happens before - * the render_block. - * - * @see gutenberg_enqueue_block_support_styles() - - private function enqueue_block_support_styles() { - $action_hook_name = 'wp_footer'; - if ( wp_is_block_theme() ) { - $action_hook_name = 'wp_head'; - } - add_action( - $action_hook_name, - array( $this, 'output_registered_block_support_styles' ) - ); - } */ - - diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index a1961241047213..76f0a6989ea97e 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -30,6 +30,13 @@ class WP_Style_Engine { */ private static $instance = null; + /** + * Registered styles. + * + * @var WP_Style_Engine_Store|null + */ + private $block_supports_store = null; + /** * Style definitions that contain the instructions to * parse/output valid Gutenberg styles from a block's attributes. @@ -215,6 +222,13 @@ class WP_Style_Engine { ), ); + /** + * Gather internals. + */ + public function __construct() { + $this->block_supports_store = new WP_Style_Engine_Store( 'block_supports' ); + } + /** * Utility method to retrieve the main instance of the class. * @@ -230,6 +244,19 @@ public static function get_instance() { return self::$instance; } + /** + * Global public interface to register block support styles support. + * + * @access private + * + * @param string $key Unique key for a $style_data object. + * @param array $style_data Associative array of style information. + * @return void + */ + public function register_block_support_styles( $key, $style_data ) { + $this->block_supports_store->register( $key, $style_data ); + } + /** * Extracts the slug in kebab case from a preset string, e.g., "heavenly-blue" from 'var:preset|color|heavenlyBlue'. * @@ -513,3 +540,21 @@ function wp_style_engine_generate( $block_styles, $options = array() ) { } return null; } + +// @TODO Just testing! +/** + * Global public interface to register block support styles support. + * + * @access public + * + * @param string $key Unique key for a $style_data object. + * @param array $style_data Associative array of style information. + * @return void + */ +function wp_style_engine_register_block_support_styles( $key, $style_data ) { + if ( class_exists( 'WP_Style_Engine' ) ) { + $style_engine = WP_Style_Engine::get_instance(); + $style_engine->register_block_support_styles( $key, $style_data ); + } +} + From a55525a971b50eb0fa1b14dcb79286cad1dc02e7 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Wed, 22 Jun 2022 11:47:16 +1000 Subject: [PATCH 3/7] Testing rendering using elements --- lib/block-supports/elements.php | 8 ++-- lib/load.php | 8 ++++ .../class-wp-style-engine-renderer.php | 30 +++++-------- .../class-wp-style-engine-store.php | 3 ++ .../style-engine/class-wp-style-engine.php | 43 +++++++++++++++---- 5 files changed, 61 insertions(+), 31 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 9230fd48b1c102..991459fadd2f45 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -105,7 +105,7 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; if ( $link_block_styles ) { - $styles = gutenberg_style_engine_generate( + gutenberg_style_engine_enqueue_block_support_styles( $link_block_styles, array( 'selector' => ".$class_name a", @@ -113,9 +113,9 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { ) ); - if ( ! empty( $styles['css'] ) ) { - gutenberg_enqueue_block_support_styles( $styles['css'] ); - } +// if ( ! empty( $styles['css'] ) ) { +// gutenberg_enqueue_block_support_styles( $styles['css'] ); +// } } return null; diff --git a/lib/load.php b/lib/load.php index f7cad4597b6928..7d4ee33f4767a7 100644 --- a/lib/load.php +++ b/lib/load.php @@ -157,6 +157,14 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-gutenberg.php'; } +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-store-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-store-gutenberg.php'; +} + +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-renderer-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-renderer-gutenberg.php'; +} + // Block supports overrides. require __DIR__ . '/block-supports/utils.php'; require __DIR__ . '/block-supports/elements.php'; diff --git a/packages/style-engine/class-wp-style-engine-renderer.php b/packages/style-engine/class-wp-style-engine-renderer.php index fb9d5885f228a7..6998e6191c7c01 100644 --- a/packages/style-engine/class-wp-style-engine-renderer.php +++ b/packages/style-engine/class-wp-style-engine-renderer.php @@ -19,33 +19,25 @@ * @access private */ class WP_Style_Engine_Renderer { - /** - * Constructor. - * - * @return void - */ - public function __construct() { - // @TODO some argument to determine how/where the styles are rendered. - // For example, we could enqueue specific inline styles like global styles, see: gutenberg_enqueue_global_styles(). - // - } - /** * Prints registered styles in the page head or footer. * + * @TODO this shares code with the styles engine class in generate(). Centralize. + * * @see $this->enqueue_block_support_styles */ - public function render_registered_styles( $styles ) { - if ( empty( $styles ) ) { + public static function render_registered_block_supports_styles() { + $style_engine = WP_Style_Engine::get_instance(); + $block_support_styles = $style_engine->get_block_support_styles(); + + if ( empty( $block_support_styles ) ) { return; } $output = ''; - foreach ( $styles as $selector => $rules ) { - $output .= "\t$selector { "; - $output .= implode( ' ', $rules ); - $output .= " }\n"; + foreach ( $block_support_styles as $style ) { + $output .= "{$style['sanitized_output']}\n"; } echo "\n"; @@ -64,14 +56,14 @@ public function render_registered_styles( $styles ) { * * @see gutenberg_enqueue_block_support_styles() */ - private function enqueue_block_support_styles() { + public static function enqueue_block_support_styles() { $action_hook_name = 'wp_footer'; if ( wp_is_block_theme() ) { $action_hook_name = 'wp_head'; } add_action( $action_hook_name, - array( $this, 'render_registered_styles' ) + array( __CLASS__, 'render_registered_block_supports_styles' ) ); } } diff --git a/packages/style-engine/class-wp-style-engine-store.php b/packages/style-engine/class-wp-style-engine-store.php index df0d1d1b657968..5f6687d6591526 100644 --- a/packages/style-engine/class-wp-style-engine-store.php +++ b/packages/style-engine/class-wp-style-engine-store.php @@ -45,6 +45,9 @@ public function __construct( $store_key = null ) { if ( ! empty( $store_key ) ) { $this->store_key = $store_key; } + + // Render engine for styles. + WP_Style_Engine_Renderer::enqueue_block_support_styles(); } /** diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 76f0a6989ea97e..d7ed9002a7fb11 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -257,6 +257,17 @@ public function register_block_support_styles( $key, $style_data ) { $this->block_supports_store->register( $key, $style_data ); } + /** + * Returns all registered block supports styles + * + * @access private + * + * @return array All registered block support styles. + */ + public function get_block_support_styles() { + return $this->block_supports_store->get(); + } + /** * Extracts the slug in kebab case from a preset string, e.g., "heavenly-blue" from 'var:preset|color|heavenlyBlue'. * @@ -402,9 +413,10 @@ public function generate( $block_styles, $options ) { return null; } - $css_rules = array(); - $classnames = array(); - $should_return_css_vars = isset( $options['css_vars'] ) && true === $options['css_vars']; + $css_rules = array(); + $classnames = array(); + $should_return_css_vars = isset( $options['css_vars'] ) && true === $options['css_vars']; + $should_register_and_enqueue = isset( $options['enqueue'] ) ? $options['enqueue'] : null; // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) { @@ -446,6 +458,19 @@ public function generate( $block_styles, $options ) { $style_block .= implode( ' ', $css ); $style_block .= ' }'; $styles_output['css'] = $style_block; + + if ( $should_register_and_enqueue ) { + // @TODO this could all change. Maybe we'd want to sanitize and build the rules later. + // Or return $css_rules here so another method can register. + // Just doing it all here for convenience while testing. + $this->register_block_support_styles( + $selector, + array( + 'rules' => $css_rules, + 'sanitized_output' => $styles_output['css'], + ) + ); + } } else { $styles_output['css'] = implode( ' ', $css ); } @@ -543,18 +568,20 @@ function wp_style_engine_generate( $block_styles, $options = array() ) { // @TODO Just testing! /** - * Global public interface to register block support styles support. + * Global public interface to register block support styles support to be output in the frontend. * * @access public * - * @param string $key Unique key for a $style_data object. - * @param array $style_data Associative array of style information. + * @param array $block_styles An array of styles from a block's attributes. + * @param array $options An array of options to determine the output. * @return void */ -function wp_style_engine_register_block_support_styles( $key, $style_data ) { +function wp_style_engine_enqueue_block_support_styles( $block_styles, $options = array() ) { if ( class_exists( 'WP_Style_Engine' ) ) { $style_engine = WP_Style_Engine::get_instance(); - $style_engine->register_block_support_styles( $key, $style_data ); + // Get style rules, then register/enqueue. + $new_options = array_merge( $options, array( 'enqueue' => true ) ); + $style_engine->generate( $block_styles, $new_options ); } } From cd3d2e9efab05716e7b922c67a2fd59903667b74 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Wed, 22 Jun 2022 11:58:18 +1000 Subject: [PATCH 4/7] importing classes to the test file --- packages/style-engine/phpunit/class-wp-style-engine-test.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index e8274e85425fa3..c3da010dd1a123 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -7,6 +7,8 @@ */ require __DIR__ . '/../class-wp-style-engine.php'; +require __DIR__ . '/../class-wp-style-engine-store.php'; +require __DIR__ . '/../class-wp-style-engine-renderer.php'; /** * Tests for registering, storing and generating styles. From 0c9c2cf514d3651487dbbe2a4c37f0e7c9a6b4d2 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 24 Jun 2022 09:53:40 +1000 Subject: [PATCH 5/7] Renaming things, commenting, just playing around for now. Architecture need more thought. --- .../class-wp-style-engine-renderer.php | 96 +++++++++++++++++-- .../class-wp-style-engine-store.php | 23 ----- .../style-engine/class-wp-style-engine.php | 61 +++++------- 3 files changed, 112 insertions(+), 68 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-renderer.php b/packages/style-engine/class-wp-style-engine-renderer.php index 6998e6191c7c01..5be15eec3ae766 100644 --- a/packages/style-engine/class-wp-style-engine-renderer.php +++ b/packages/style-engine/class-wp-style-engine-renderer.php @@ -1,10 +1,12 @@ $css_definitions ) { + $output .= self::generate_css_rule( $selector, $css_definitions, true ); } echo "\n"; } + /** + * Filters incoming CSS properties against WordPress Core's allowed CSS attributes in wp-includes/kses.php. + * + * @param string $property_declaration A CSS property declaration, e.g., `color: 'pink'`. + * + * @return string A filtered CSS property. Empty if not allowed. + */ + public static function sanitize_property_declaration( $property_declaration ) { + return esc_html( safecss_filter_attr( $property_declaration ) ); + } + + /** + * Creates a string consisting of CSS property declarations suitable for the value of an HTML element's style attribute. + * + * @param array $css_definitions An collection of CSS definitions `[ [ 'color' => 'red' ] ]`. + * + * @return string A concatenated string of CSS properties, e.g. `'color: red; font-size:12px'` + */ + public static function generate_inline_property_declarations( $css_definitions ) { + $css_rule_inline = ''; + + if ( empty( $css_definitions ) ) { + return $css_rule_inline; + } + foreach ( $css_definitions as $definition => $value ) { + $filtered_css = self::sanitize_property_declaration( "{$definition}: {$value}" ); + if ( ! empty( $filtered_css ) ) { + $css_rule_inline .= $filtered_css . ';'; + } + } + return $css_rule_inline; + } + + /** + * Creates a string consisting of a CSS rule + * + * @param string $selector A CSS selector, e.g., `.some-class-name`. + * @param array $css_definitions An collection of CSS definitions `[ [ 'color' => 'red' ] ]`. + * @param boolean $should_prettify Whether to print spaces and carriage returns. + * + * @return string A CSS rule, e.g. `'.some-selector { color: red; font-size:12px }'` + */ + public static function generate_css_rule( $selector, $css_definitions, $should_prettify = false ) { + $css_rule_block = ''; + + if ( ! $selector || empty( $css_definitions ) ) { + return $css_rule_block; + } + + $css_rule_block = $should_prettify ? "$selector {\n" : "$selector { "; + + foreach ( $css_definitions as $definition => $value ) { + $filtered_css = self::sanitize_property_declaration( "{$definition}: {$value}" ); + if ( ! empty( $filtered_css ) ) { + if ( $should_prettify ) { + $css_rule_block .= "\t$filtered_css;\n"; + } else { + $css_rule_block .= $filtered_css . ';'; + } + } + } + $css_rule_block .= $should_prettify ? "}\n" : ' }'; + return $css_rule_block; + } + + // @TODO The following method takes over the work of enqueuing block support styles for now. + // Later we'd want to identify which styles we're rendering, e.g., is this a global styles ruleset, + // so we can create appropriate "layers" / control specificity. + // We could create separate blocks for each layer, e.g., + // wp_register_style( 'global-styles-layer', false, array(), true, true ); + // wp_add_inline_style( 'global-styles-layer', $styles ); + // wp_enqueue_style( 'global-styles-layer' ); + // Or build them and print them all out in one. + // Just how, I don't know right now :D. + /** * Taken from gutenberg_enqueue_block_support_styles() * @@ -55,15 +132,18 @@ public static function render_registered_block_supports_styles() { * the render_block. * * @see gutenberg_enqueue_block_support_styles() + * + * @param int $priority To set the priority for the add_action. */ - public static function enqueue_block_support_styles() { + public static function enqueue_block_support_styles( $priority = 10 ) { $action_hook_name = 'wp_footer'; if ( wp_is_block_theme() ) { $action_hook_name = 'wp_head'; } add_action( $action_hook_name, - array( __CLASS__, 'render_registered_block_supports_styles' ) + array( __CLASS__, 'render_registered_block_supports_styles' ), + $priority ); } } diff --git a/packages/style-engine/class-wp-style-engine-store.php b/packages/style-engine/class-wp-style-engine-store.php index 5f6687d6591526..0a974cd675694b 100644 --- a/packages/style-engine/class-wp-style-engine-store.php +++ b/packages/style-engine/class-wp-style-engine-store.php @@ -28,28 +28,6 @@ class WP_Style_Engine_Store { */ private $registered_styles = array(); - /** - * A key to identify the store. Default value is 'global'. - * - * @var WP_Style_Engine_Store|null - */ - private $store_key = 'global-styles'; - - /** - * Constructor. - * - * @param string $store_key A key/name/id to identify the store. - * @return void - */ - public function __construct( $store_key = null ) { - if ( ! empty( $store_key ) ) { - $this->store_key = $store_key; - } - - // Render engine for styles. - WP_Style_Engine_Renderer::enqueue_block_support_styles(); - } - /** * Register a style * @@ -72,7 +50,6 @@ public function register( $key, $style_data ) { * Retrieves style data from the store. * * @param string $key Optional unique key for a $style_data object to return a single style object. - * @param array? $style_data Associative array of style information. * * @return array Registered styles */ diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index d7ed9002a7fb11..71fb065f8d364b 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -226,7 +226,12 @@ class WP_Style_Engine { * Gather internals. */ public function __construct() { - $this->block_supports_store = new WP_Style_Engine_Store( 'block_supports' ); + // @TODO not sure where to instantiate stuff yet. + // The hope is to have several "stores" for each layer of the style hierarchy. + // This is so we can control the order in which we render. + // Each store might have a unique way to render on the frontend, maybe not. + $this->block_supports_store = new WP_Style_Engine_Store(); + WP_Style_Engine_Renderer::enqueue_block_support_styles(); } /** @@ -413,7 +418,7 @@ public function generate( $block_styles, $options ) { return null; } - $css_rules = array(); + $css_definitions = array(); $classnames = array(); $should_return_css_vars = isset( $options['css_vars'] ) && true === $options['css_vars']; $should_register_and_enqueue = isset( $options['enqueue'] ) ? $options['enqueue'] : null; @@ -430,50 +435,32 @@ public function generate( $block_styles, $options ) { continue; } - $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - $css_rules = array_merge( $css_rules, static::get_css( $style_value, $style_definition, $should_return_css_vars ) ); + $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); + $css_definitions = array_merge( $css_definitions, static::get_css( $style_value, $style_definition, $should_return_css_vars ) ); } } // Build CSS rules output. $selector = isset( $options['selector'] ) ? $options['selector'] : null; - $css = array(); $styles_output = array(); - if ( ! empty( $css_rules ) ) { - // Generate inline style rules. - foreach ( $css_rules as $rule => $value ) { - $filtered_css = esc_html( safecss_filter_attr( "{$rule}: {$value}" ) ); - if ( ! empty( $filtered_css ) ) { - $css[] = $filtered_css . ';'; - } - } + if ( $selector && $should_register_and_enqueue ) { + // @TODO this could all change. Maybe we'd want to sanitize and build the rules later. + // Or return $css_rules here so another method can register. + // Just doing it all here for convenience while testing. + $this->register_block_support_styles( + $selector, + $css_definitions + ); + return null; } - // Return css, if any. - if ( ! empty( $css ) ) { - // Return an entire rule if there is a selector. - if ( $selector ) { - $style_block = "$selector { "; - $style_block .= implode( ' ', $css ); - $style_block .= ' }'; - $styles_output['css'] = $style_block; - - if ( $should_register_and_enqueue ) { - // @TODO this could all change. Maybe we'd want to sanitize and build the rules later. - // Or return $css_rules here so another method can register. - // Just doing it all here for convenience while testing. - $this->register_block_support_styles( - $selector, - array( - 'rules' => $css_rules, - 'sanitized_output' => $styles_output['css'], - ) - ); - } - } else { - $styles_output['css'] = implode( ' ', $css ); - } + // Return rendered CSS. + // Consider dependency injection of `WP_Style_Engine_Renderer` for testability. + if ( $selector ) { + $styles_output['css'] = WP_Style_Engine_Renderer::generate_css_rule( $selector, $css_definitions ); + } else { + $styles_output['css'] = WP_Style_Engine_Renderer::generate_inline_property_declarations( $css_definitions ); } // Return classnames, if any. From 42f443692d9a198a9eba3a6649c8308c56801481 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 24 Jun 2022 15:59:26 +1000 Subject: [PATCH 6/7] Fooling around with layers. Temporary! --- lib/block-supports/elements.php | 4 +- .../class-wp-style-engine-renderer.php | 2 +- .../class-wp-style-engine-store.php | 32 +++++++++++---- .../style-engine/class-wp-style-engine.php | 41 +++++++++++++------ 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 991459fadd2f45..19943c73785243 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -105,11 +105,11 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; if ( $link_block_styles ) { - gutenberg_style_engine_enqueue_block_support_styles( + gutenberg_style_engine_enqueue_styles( $link_block_styles, array( 'selector' => ".$class_name a", - 'css_vars' => true, + 'layer' => 'block-supports', ) ); diff --git a/packages/style-engine/class-wp-style-engine-renderer.php b/packages/style-engine/class-wp-style-engine-renderer.php index 5be15eec3ae766..4db6c74090f4f7 100644 --- a/packages/style-engine/class-wp-style-engine-renderer.php +++ b/packages/style-engine/class-wp-style-engine-renderer.php @@ -30,7 +30,7 @@ class WP_Style_Engine_Renderer { */ public static function render_registered_block_supports_styles() { $style_engine = WP_Style_Engine::get_instance(); - $block_support_styles = $style_engine->get_block_support_styles(); + $block_support_styles = $style_engine->get_registered_styles( 'block-supports' ); if ( empty( $block_support_styles ) ) { return; diff --git a/packages/style-engine/class-wp-style-engine-store.php b/packages/style-engine/class-wp-style-engine-store.php index 0a974cd675694b..a03954e2d4fc99 100644 --- a/packages/style-engine/class-wp-style-engine-store.php +++ b/packages/style-engine/class-wp-style-engine-store.php @@ -28,35 +28,51 @@ class WP_Style_Engine_Store { */ private $registered_styles = array(); + /** + * Gather internals. + */ + public function __construct( $layers = array() ) { + foreach ( $layers as $layer ) { + $this->registered_styles[ $layer ] = array(); + } + } + /** * Register a style * + * @param string $layer Unique key for a layer. * @param string $key Unique key for a $style_data object. * @param array $style_data Associative array of style information. * @return void */ - public function register( $key, $style_data ) { - if ( empty( $key ) || empty( $style_data ) ) { + public function register( $layer, $key, $style_data ) { + if ( empty( $layer ) || empty( $key ) || empty( $style_data ) ) { return; } - if ( isset( $this->registered_styles[ $key ] ) ) { - $style_data = array_unique( array_merge( $this->registered_styles[ $key ], $style_data ) ); + if ( isset( $this->registered_styles[ $layer ][ $key ] ) ) { + $style_data = array_unique( array_merge( $this->registered_styles[ $layer ][ $key ], $style_data ) ); } - $this->registered_styles[ $key ] = $style_data; + $this->registered_styles[ $layer ][ $key ] = $style_data; } /** * Retrieves style data from the store. * + * @param string $layer Unique key for a layer. * @param string $key Optional unique key for a $style_data object to return a single style object. * * @return array Registered styles */ - public function get( $key = null ) { - if ( isset( $this->registered_styles[ $key ] ) ) { - return $this->registered_styles[ $key ]; + public function get( $layer = null, $key = null ) { + if ( isset( $this->registered_styles[ $layer ][ $key ] ) ) { + return $this->registered_styles[ $layer ][ $key ]; + } + + if ( isset( $this->registered_styles[ $layer ] ) ) { + return $this->registered_styles[ $layer ]; } + return $this->registered_styles; } } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 71fb065f8d364b..af7198b9878407 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -35,7 +35,16 @@ class WP_Style_Engine { * * @var WP_Style_Engine_Store|null */ - private $block_supports_store = null; + private $styles_store = null; + + /** + * An ordered list of style layers from least specific to most specific. + * The layers loosely represent a cascade layer system. + * The layer name will also be the key to retrieve the corresponding styles from the store. + */ + const STYLE_LAYERS = array( + 'block-supports', // User-defined block-level overrides. + ); /** * Style definitions that contain the instructions to @@ -226,11 +235,11 @@ class WP_Style_Engine { * Gather internals. */ public function __construct() { - // @TODO not sure where to instantiate stuff yet. + // @TODO not sure where to instantiate stuff or whether to dependency inject yet. // The hope is to have several "stores" for each layer of the style hierarchy. // This is so we can control the order in which we render. // Each store might have a unique way to render on the frontend, maybe not. - $this->block_supports_store = new WP_Style_Engine_Store(); + $this->styles_store = new WP_Style_Engine_Store( self::STYLE_LAYERS ); WP_Style_Engine_Renderer::enqueue_block_support_styles(); } @@ -254,12 +263,13 @@ public static function get_instance() { * * @access private * + * @param string $layer Unique key for a layer. * @param string $key Unique key for a $style_data object. * @param array $style_data Associative array of style information. * @return void */ - public function register_block_support_styles( $key, $style_data ) { - $this->block_supports_store->register( $key, $style_data ); + public function register_styles( $layer, $key, $style_data ) { + $this->styles_store->register( $layer, $key, $style_data ); } /** @@ -267,10 +277,11 @@ public function register_block_support_styles( $key, $style_data ) { * * @access private * + * @param string $layer Unique key for a layer. * @return array All registered block support styles. */ - public function get_block_support_styles() { - return $this->block_supports_store->get(); + public function get_registered_styles( $layer ) { + return $this->styles_store->get( $layer ); } /** @@ -422,6 +433,7 @@ public function generate( $block_styles, $options ) { $classnames = array(); $should_return_css_vars = isset( $options['css_vars'] ) && true === $options['css_vars']; $should_register_and_enqueue = isset( $options['enqueue'] ) ? $options['enqueue'] : null; + $registry_layer_key = isset( $options['layer'] ) && in_array( $options['layer'], self::STYLE_LAYERS, true ) ? $options['layer'] : null; // Collect CSS and classnames. foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) { @@ -444,11 +456,12 @@ public function generate( $block_styles, $options ) { $selector = isset( $options['selector'] ) ? $options['selector'] : null; $styles_output = array(); - if ( $selector && $should_register_and_enqueue ) { + if ( $registry_layer_key && $selector && $should_register_and_enqueue ) { // @TODO this could all change. Maybe we'd want to sanitize and build the rules later. // Or return $css_rules here so another method can register. // Just doing it all here for convenience while testing. - $this->register_block_support_styles( + $this->register_styles( + $registry_layer_key, $selector, $css_definitions ); @@ -563,11 +576,15 @@ function wp_style_engine_generate( $block_styles, $options = array() ) { * @param array $options An array of options to determine the output. * @return void */ -function wp_style_engine_enqueue_block_support_styles( $block_styles, $options = array() ) { +function wp_style_engine_enqueue_styles( $block_styles, $options = array() ) { if ( class_exists( 'WP_Style_Engine' ) ) { $style_engine = WP_Style_Engine::get_instance(); - // Get style rules, then register/enqueue. - $new_options = array_merge( $options, array( 'enqueue' => true ) ); + $defaults = array( + 'enqueue' => true, + 'css_vars' => true, + ); + $new_options = wp_parse_args( $options, $defaults ); + $style_engine->generate( $block_styles, $new_options ); } } From c72366e7c94f7ca94613a89c12c3e93c665c18f1 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Mon, 27 Jun 2022 14:14:18 +1000 Subject: [PATCH 7/7] Experimenting with cascade layers --- lib/block-supports/elements.php | 4 - .../class-wp-style-engine-renderer.php | 88 ++++++++++++++----- .../class-wp-style-engine-store.php | 10 ++- .../style-engine/class-wp-style-engine.php | 15 ++-- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 19943c73785243..6f8b8816cf51a9 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -112,10 +112,6 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { 'layer' => 'block-supports', ) ); - -// if ( ! empty( $styles['css'] ) ) { -// gutenberg_enqueue_block_support_styles( $styles['css'] ); -// } } return null; diff --git a/packages/style-engine/class-wp-style-engine-renderer.php b/packages/style-engine/class-wp-style-engine-renderer.php index 4db6c74090f4f7..4cefd248753a9b 100644 --- a/packages/style-engine/class-wp-style-engine-renderer.php +++ b/packages/style-engine/class-wp-style-engine-renderer.php @@ -30,7 +30,7 @@ class WP_Style_Engine_Renderer { */ public static function render_registered_block_supports_styles() { $style_engine = WP_Style_Engine::get_instance(); - $block_support_styles = $style_engine->get_registered_styles( 'block-supports' ); + $block_support_styles = $style_engine->get_registered_styles(); if ( empty( $block_support_styles ) ) { return; @@ -39,7 +39,7 @@ public static function render_registered_block_supports_styles() { $output = ''; foreach ( $block_support_styles as $selector => $css_definitions ) { - $output .= self::generate_css_rule( $selector, $css_definitions, true ); + $output .= self::generate_css_rule( $selector, $css_definitions, array( 'prettify' => true ) ); } echo "\n"; @@ -79,46 +79,89 @@ public static function generate_inline_property_declarations( $css_definitions ) } /** - * Creates a string consisting of a CSS rule + * Creates a string consisting of a CSS rule. * - * @param string $selector A CSS selector, e.g., `.some-class-name`. - * @param array $css_definitions An collection of CSS definitions `[ [ 'color' => 'red' ] ]`. - * @param boolean $should_prettify Whether to print spaces and carriage returns. + * @param string $selector A CSS selector, e.g., `.some-class-name`. + * @param array $css_definitions An collection of CSS definitions `[ [ 'color' => 'red' ] ]`. + * @param array $options array( + * 'prettify' => (boolean) Whether to add carriage returns and indenting. + * 'indent' => (number) The number of tab indents to apply to the rule. Applies if `prettify` is `true`. + * );. * * @return string A CSS rule, e.g. `'.some-selector { color: red; font-size:12px }'` */ - public static function generate_css_rule( $selector, $css_definitions, $should_prettify = false ) { + public static function generate_css_rule( $selector, $css_definitions, $options = array() ) { $css_rule_block = ''; if ( ! $selector || empty( $css_definitions ) ) { return $css_rule_block; } - $css_rule_block = $should_prettify ? "$selector {\n" : "$selector { "; + $defaults = array( + 'prettify' => false, + 'indent' => 0, + ); + $options = wp_parse_args( $options, $defaults ); + $indent = str_repeat( "\t", $options['indent'] ); + $css_rule_block = $options['prettify'] ? "$indent$selector {\n" : "$selector { "; foreach ( $css_definitions as $definition => $value ) { $filtered_css = self::sanitize_property_declaration( "{$definition}: {$value}" ); if ( ! empty( $filtered_css ) ) { - if ( $should_prettify ) { - $css_rule_block .= "\t$filtered_css;\n"; + if ( $options['prettify'] ) { + $css_rule_block .= "\t$indent$filtered_css;\n"; } else { $css_rule_block .= $filtered_css . ';'; } } } - $css_rule_block .= $should_prettify ? "}\n" : ' }'; + $css_rule_block .= $options['prettify'] ? "$indent}\n" : ' }'; return $css_rule_block; } - // @TODO The following method takes over the work of enqueuing block support styles for now. - // Later we'd want to identify which styles we're rendering, e.g., is this a global styles ruleset, - // so we can create appropriate "layers" / control specificity. - // We could create separate blocks for each layer, e.g., - // wp_register_style( 'global-styles-layer', false, array(), true, true ); - // wp_add_inline_style( 'global-styles-layer', $styles ); - // wp_enqueue_style( 'global-styles-layer' ); - // Or build them and print them all out in one. - // Just how, I don't know right now :D. + // @TODO Using cascade layers should be opt-in. + /** + * Builds layers and styles rules from registered layers and styles for output. + */ + public static function enqueue_cascade_layers() { + $style_engine = WP_Style_Engine::get_instance(); + $registered_layers = $style_engine->get_registered_styles(); + + if ( empty( $registered_layers ) ) { + return; + } + + $layer_output = array(); + $styles_output = ''; + + foreach ( $style_engine::STYLE_LAYERS as $layer_name ) { + if ( ! isset( $registered_layers[ $layer_name ] ) || empty( $registered_layers[ $layer_name ] ) ) { + continue; + } + + $layer_output[] = $layer_name; + $styles_output .= "@layer {$layer_name} {\n"; + + foreach ( $registered_layers[ $layer_name ] as $selector => $css_definitions ) { + $styles_output .= self::generate_css_rule( + $selector, + $css_definitions, + array( + 'prettify' => true, + 'indent' => 1, + ) + ); + } + $styles_output .= '}'; + } + + if ( ! empty( $styles_output ) ) { + $layer_output = '@layer ' . implode( ', ', $layer_output ) . ";\n"; + wp_register_style( 'wp-styles-layers', false, array(), true, true ); + wp_add_inline_style( 'wp-styles-layers', $layer_output . $styles_output ); + wp_enqueue_style( 'wp-styles-layers' ); + } + } /** * Taken from gutenberg_enqueue_block_support_styles() @@ -135,14 +178,15 @@ public static function generate_css_rule( $selector, $css_definitions, $should_p * * @param int $priority To set the priority for the add_action. */ - public static function enqueue_block_support_styles( $priority = 10 ) { + public static function enqueue_registered_styles( $priority = 10 ) { $action_hook_name = 'wp_footer'; if ( wp_is_block_theme() ) { $action_hook_name = 'wp_head'; } + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_cascade_layers' ) ); add_action( $action_hook_name, - array( __CLASS__, 'render_registered_block_supports_styles' ), + array( __CLASS__, 'enqueue_cascade_layers' ), $priority ); } diff --git a/packages/style-engine/class-wp-style-engine-store.php b/packages/style-engine/class-wp-style-engine-store.php index a03954e2d4fc99..186bfb76be155d 100644 --- a/packages/style-engine/class-wp-style-engine-store.php +++ b/packages/style-engine/class-wp-style-engine-store.php @@ -43,23 +43,25 @@ public function __construct( $layers = array() ) { * @param string $layer Unique key for a layer. * @param string $key Unique key for a $style_data object. * @param array $style_data Associative array of style information. - * @return void + * @return boolean Whether registration was successful. */ public function register( $layer, $key, $style_data ) { if ( empty( $layer ) || empty( $key ) || empty( $style_data ) ) { - return; + return false; } if ( isset( $this->registered_styles[ $layer ][ $key ] ) ) { $style_data = array_unique( array_merge( $this->registered_styles[ $layer ][ $key ], $style_data ) ); } $this->registered_styles[ $layer ][ $key ] = $style_data; + return true; } /** - * Retrieves style data from the store. + * Retrieves style data from the store. If neither $layer nor $key are provided, + * this method will return everything in the store. * - * @param string $layer Unique key for a layer. + * @param string $layer Optional unique key for a layer to return all styles for a layer. * @param string $key Optional unique key for a $style_data object to return a single style object. * * @return array Registered styles diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index af7198b9878407..35a3c5ba9efdc1 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -2,6 +2,7 @@ /** * WP_Style_Engine * + * Singleton. * Generates classnames and block styles. * * @package Gutenberg @@ -39,8 +40,11 @@ class WP_Style_Engine { /** * An ordered list of style layers from least specific to most specific. - * The layers loosely represent a cascade layer system. + * The layers loosely represent a cascade layer system. See: https://developer.mozilla.org/en-US/docs/Web/CSS/@layer * The layer name will also be the key to retrieve the corresponding styles from the store. + * Using a "store" for each layer of the style hierarchy, one can control the order in which they're rendered. + * + * @TODO are static, named layers dynamic enough? Or should we define numerically according specificity? E.g., @wp-layer-1? */ const STYLE_LAYERS = array( 'block-supports', // User-defined block-level overrides. @@ -235,12 +239,9 @@ class WP_Style_Engine { * Gather internals. */ public function __construct() { - // @TODO not sure where to instantiate stuff or whether to dependency inject yet. - // The hope is to have several "stores" for each layer of the style hierarchy. - // This is so we can control the order in which we render. - // Each store might have a unique way to render on the frontend, maybe not. + // @TODO not sure where keep as singleton or whether to dependency inject yet. $this->styles_store = new WP_Style_Engine_Store( self::STYLE_LAYERS ); - WP_Style_Engine_Renderer::enqueue_block_support_styles(); + WP_Style_Engine_Renderer::enqueue_registered_styles(); } /** @@ -280,7 +281,7 @@ public function register_styles( $layer, $key, $style_data ) { * @param string $layer Unique key for a layer. * @return array All registered block support styles. */ - public function get_registered_styles( $layer ) { + public function get_registered_styles( $layer = null ) { return $this->styles_store->get( $layer ); }