From 4ca1d9bc9017f42aa84080148ff0222a9418d3b2 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 7 Jul 2022 10:48:36 +0300 Subject: [PATCH 01/17] Add a CSS_Rule object --- lib/load.php | 3 + .../class-wp-style-engine-css-rule.php | 107 ++++++++++++++++++ tools/webpack/packages.js | 1 + 3 files changed, 111 insertions(+) create mode 100644 packages/style-engine/class-wp-style-engine-css-rule.php diff --git a/lib/load.php b/lib/load.php index b125ef04913a62..7c68df0a9803a5 100644 --- a/lib/load.php +++ b/lib/load.php @@ -116,7 +116,10 @@ function gutenberg_is_experiment_enabled( $name ) { if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-declarations-gutenberg.php' ) ) { require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-declarations-gutenberg.php'; } +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-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-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php new file mode 100644 index 00000000000000..70195382458468 --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -0,0 +1,107 @@ +set_selector( $selector ); + $this->set_parent( $parent ); + $this->set_declarations( $declarations ); + } + + /** + * Set the selector. + * + * @param string $selector The CSS selector. + */ + public function set_selector( $selector ) { + $this->selector = $selector; + } + + /** + * Set the parent selector. + * + * @param WP_Style_Engine_CSS_Rule $parent The parent rule. + */ + public function set_parent( $parent ) { + $this->parent = $parent; + } + + /** + * Set the declarations. + * + * @param WP_Style_Engine_CSS_Declarations[] $declarations An array of WP_Style_Engine_CSS_Declarations objects. + */ + public function set_declarations( $declarations ) { + if ( $declarations instanceof WP_Style_Engine_CSS_Declarations ) { + $declarations = array( $declarations ); + } + $this->declarations = array_merge( $this->$declarations, $declarations ); + } + + /** + * Get the parent selector. + * + * @return string + */ + public function get_parent_selector() { + return $this->parent ? $this->parent->get_selector() : ''; + } + + /** + * Get the full selector. + * + * @return string + */ + public function get_selector() { + if ( 0 === strpos( $this->selector, '&' ) ) { + return $this->get_parent_selector() . substr( $this->selector, 1 ); + } + return $this->get_parent_selector() . ' ' . $this->selector; + } +} diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index c13ceea5de9ecc..978333f880d43a 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -37,6 +37,7 @@ const bundledPackagesPhpConfig = [ to: 'build/style-engine/', replaceClasses: [ 'WP_Style_Engine_CSS_Declarations', + 'WP_Style_Engine_CSS_Rule', 'WP_Style_Engine', ], }, From df1528110232fb6dec9e60b065d4a1f796c00b2f Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 7 Jul 2022 11:26:35 +0300 Subject: [PATCH 02/17] Add a rules store --- lib/load.php | 5 +- .../class-wp-style-engine-css-rules-store.php | 138 ++++++++++++++++++ tools/webpack/packages.js | 1 + 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 packages/style-engine/class-wp-style-engine-css-rules-store.php diff --git a/lib/load.php b/lib/load.php index 7c68df0a9803a5..57f7993440f57a 100644 --- a/lib/load.php +++ b/lib/load.php @@ -118,8 +118,11 @@ function gutenberg_is_experiment_enabled( $name ) { } if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php' ) ) { require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php'; - } +if ( file_exists( __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php' ) ) { + require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-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-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php new file mode 100644 index 00000000000000..acdd3a560bfa13 --- /dev/null +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -0,0 +1,138 @@ + $selector_part ) { + // Get the parent object if needed. + $parent = 0 === $key ? null : static::get_rule( $selector_parts[ $full_selector ] ); + // Compile the full selector for the store. + $full_selector .= ( 0 === strpos( $selector_part, '&' ) ) ? substr( $selector_part, 1 ) : ' ' . $selector_part; + if ( 0 === $key ) { + $full_selector = ltrim( $full_selector ); + } + // Create the rule if it doesn't exist. + if ( empty( static::$rules[ $full_selector ] ) ) { + static::$rules[ $full_selector ] = new WP_Style_Engine_CSS_Rule( $selector_part, $parent ); + } + } + + return static::$rules[ $selector ]; + } + + /** + * Deconstruct a selector to its parts. + * + * @static + * + * @param string $selector The CSS selector. + * + * @return array An array of selector parts. + */ + public static function deconstruct_selector( $selector ) { + $selector = trim( $selector ); + + // Split selector parts on space characters. + $parts = explode( ' ', $selector ); + $parts = array_map( 'trim', $parts ); + + // Remove empty parts. + $parts = array_filter( $parts ); + + // Split parts on ':' characters. + $parts = static::deconstruct_selector_array_on_character( $parts, ':' ); + + return $parts; + } + + /** + * Expands and re-structures an array based on a character. + * + * @static + * + * @param array $array The array to expand. + * @param string $character The character to expand on. + * + * @return array The expanded array. + */ + public static function deconstruct_selector_array_on_character( $array, $character ) { + $new_array = array(); + foreach ( $array as $value ) { + if ( strpos( $value, $character ) === false ) { + $new_array[] = $value; + continue; + } + + $value = str_replace( $character, "SPLIT_CHARACTER$character", $value ); + if ( ':' === $character ) { + $value = str_replace( ':', '&:', $value ); + } + $value = explode( 'SPLIT_CHARACTER', $value ); + $new_array = array_merge( $new_array, $value ); + } + return $new_array; + } + + /** + * Inject an array inside an array at a specific index. + * + * @static + * + * @param array $array The array to inject into. + * @param array $inject The array to inject. + * @param int $index The index to inject at. + */ + public static function inject_array_at_index( $array, $inject, $index ) { + $array = array_slice( $array, 0, $index, true ) + $inject + array_slice( $array, $index, count( $array ) - $index, true ); + return $array; + } +} diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index 978333f880d43a..2f6720d579452f 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -37,6 +37,7 @@ const bundledPackagesPhpConfig = [ to: 'build/style-engine/', replaceClasses: [ 'WP_Style_Engine_CSS_Declarations', + 'WP_Style_Engine_CSS_Rules_Store', 'WP_Style_Engine_CSS_Rule', 'WP_Style_Engine', ], From da2c4338bf3a95f23035e70bf59c4ee2347844c7 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 7 Jul 2022 11:56:03 +0300 Subject: [PATCH 03/17] some inline docs & cleanup on the store --- .../class-wp-style-engine-css-rules-store.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index acdd3a560bfa13..8efe50eb9a70ee 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -46,10 +46,20 @@ public static function get_all_rules() { * @param string $selector The CSS selector. */ public static function get_rule( $selector ) { + + // Bail early if there is no selector. if ( empty( $selector ) ) { return; } + // If the selector is already stored, return it. + if ( isset( static::$rules[ $selector ] ) ) { + return static::$rules[ $selector ]; + } + + // Create the selector if it doesn't exist. + // To do that, first deconstruct the selector to its parts + // and build the hierarchy of selector objects prior to storing it. $selector_parts = static::deconstruct_selector( $selector ); $full_selector = ''; foreach ( $selector_parts as $key => $selector_part ) { @@ -112,27 +122,17 @@ public static function deconstruct_selector_array_on_character( $array, $charact continue; } + // Use "SPLIT_CHARACTER" as a delimiter. $value = str_replace( $character, "SPLIT_CHARACTER$character", $value ); + + // Pseudo-selectors are a special case. if ( ':' === $character ) { $value = str_replace( ':', '&:', $value ); } + $value = explode( 'SPLIT_CHARACTER', $value ); $new_array = array_merge( $new_array, $value ); } return $new_array; } - - /** - * Inject an array inside an array at a specific index. - * - * @static - * - * @param array $array The array to inject into. - * @param array $inject The array to inject. - * @param int $index The index to inject at. - */ - public static function inject_array_at_index( $array, $inject, $index ) { - $array = array_slice( $array, 0, $index, true ) + $inject + array_slice( $array, $index, count( $array ) - $index, true ); - return $array; - } } From f6176f710fd864df89f4371a5defc7946078b9d7 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 7 Jul 2022 13:03:26 +0300 Subject: [PATCH 04/17] typos & fixes --- .../class-wp-style-engine-css-rule.php | 22 ++++++++++++++++++- .../class-wp-style-engine-css-rules-store.php | 4 +++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index 70195382458468..106507e8ecce3f 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -60,6 +60,9 @@ public function __construct( $selector = '', $parent = null, $declarations = arr * @param string $selector The CSS selector. */ public function set_selector( $selector ) { + if ( empty( $selector ) ) { + return; + } $this->selector = $selector; } @@ -69,6 +72,9 @@ public function set_selector( $selector ) { * @param WP_Style_Engine_CSS_Rule $parent The parent rule. */ public function set_parent( $parent ) { + if ( ! $parent instanceof WP_Style_Engine_CSS_Rule ) { + return; + } $this->parent = $parent; } @@ -81,7 +87,7 @@ public function set_declarations( $declarations ) { if ( $declarations instanceof WP_Style_Engine_CSS_Declarations ) { $declarations = array( $declarations ); } - $this->declarations = array_merge( $this->$declarations, $declarations ); + $this->declarations = array_merge( $this->declarations, $declarations ); } /** @@ -104,4 +110,18 @@ public function get_selector() { } return $this->get_parent_selector() . ' ' . $this->selector; } + + /** + * Get the CSS. + * + * @return string + */ + public function get_css() { + $css = $this->get_selector() . ' {'; + foreach ( $this->declarations as $declaration ) { + $css .= $declaration->get_declarations_string(); + } + $css .= '}'; + return $css; + } } diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index 8efe50eb9a70ee..31f6603c7ddfcb 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -47,6 +47,8 @@ public static function get_all_rules() { */ public static function get_rule( $selector ) { + $selector = trim( $selector ); + // Bail early if there is no selector. if ( empty( $selector ) ) { return; @@ -64,7 +66,7 @@ public static function get_rule( $selector ) { $full_selector = ''; foreach ( $selector_parts as $key => $selector_part ) { // Get the parent object if needed. - $parent = 0 === $key ? null : static::get_rule( $selector_parts[ $full_selector ] ); + $parent = 0 === $key ? null : static::get_rule( $full_selector ); // Compile the full selector for the store. $full_selector .= ( 0 === strpos( $selector_part, '&' ) ) ? substr( $selector_part, 1 ) : ' ' . $selector_part; if ( 0 === $key ) { From 0a6b4ef50308785eeab8ac887f3c0dc641710c87 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Wed, 13 Jul 2022 11:10:49 +0300 Subject: [PATCH 05/17] remove parent relations for rules --- .../class-wp-style-engine-css-rule.php | 37 +-------- .../class-wp-style-engine-css-rules-store.php | 82 +------------------ 2 files changed, 5 insertions(+), 114 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index 106507e8ecce3f..53eb4594188f52 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -25,13 +25,6 @@ class WP_Style_Engine_CSS_Rule { */ protected $selector; - /** - * The parent rule. - * - * @var WP_Style_Engine_CSS_Rule - */ - protected $parent; - /** * The selector declarations. * @@ -45,12 +38,10 @@ class WP_Style_Engine_CSS_Rule { * Constructor * * @param string $selector The CSS selector. - * @param WP_Style_Engine_CSS_Rule|null $parent The parent rule. * @param WP_Style_Engine_CSS_Declarations[] $declarations An array of WP_Style_Engine_CSS_Declarations objects. */ - public function __construct( $selector = '', $parent = null, $declarations = array() ) { + public function __construct( $selector = '', $declarations = array() ) { $this->set_selector( $selector ); - $this->set_parent( $parent ); $this->set_declarations( $declarations ); } @@ -66,18 +57,6 @@ public function set_selector( $selector ) { $this->selector = $selector; } - /** - * Set the parent selector. - * - * @param WP_Style_Engine_CSS_Rule $parent The parent rule. - */ - public function set_parent( $parent ) { - if ( ! $parent instanceof WP_Style_Engine_CSS_Rule ) { - return; - } - $this->parent = $parent; - } - /** * Set the declarations. * @@ -90,25 +69,13 @@ public function set_declarations( $declarations ) { $this->declarations = array_merge( $this->declarations, $declarations ); } - /** - * Get the parent selector. - * - * @return string - */ - public function get_parent_selector() { - return $this->parent ? $this->parent->get_selector() : ''; - } - /** * Get the full selector. * * @return string */ public function get_selector() { - if ( 0 === strpos( $this->selector, '&' ) ) { - return $this->get_parent_selector() . substr( $this->selector, 1 ); - } - return $this->get_parent_selector() . ' ' . $this->selector; + return $this->selector; } /** diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index 31f6603c7ddfcb..d0a8b1a18873c9 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -54,87 +54,11 @@ public static function get_rule( $selector ) { return; } - // If the selector is already stored, return it. - if ( isset( static::$rules[ $selector ] ) ) { - return static::$rules[ $selector ]; - } - - // Create the selector if it doesn't exist. - // To do that, first deconstruct the selector to its parts - // and build the hierarchy of selector objects prior to storing it. - $selector_parts = static::deconstruct_selector( $selector ); - $full_selector = ''; - foreach ( $selector_parts as $key => $selector_part ) { - // Get the parent object if needed. - $parent = 0 === $key ? null : static::get_rule( $full_selector ); - // Compile the full selector for the store. - $full_selector .= ( 0 === strpos( $selector_part, '&' ) ) ? substr( $selector_part, 1 ) : ' ' . $selector_part; - if ( 0 === $key ) { - $full_selector = ltrim( $full_selector ); - } - // Create the rule if it doesn't exist. - if ( empty( static::$rules[ $full_selector ] ) ) { - static::$rules[ $full_selector ] = new WP_Style_Engine_CSS_Rule( $selector_part, $parent ); - } + // Create the rule if it doesn't exist. + if ( empty( static::$rules[ $selector ] ) ) { + static::$rules[ $selector ] = new WP_Style_Engine_CSS_Rule( $selector ); } return static::$rules[ $selector ]; } - - /** - * Deconstruct a selector to its parts. - * - * @static - * - * @param string $selector The CSS selector. - * - * @return array An array of selector parts. - */ - public static function deconstruct_selector( $selector ) { - $selector = trim( $selector ); - - // Split selector parts on space characters. - $parts = explode( ' ', $selector ); - $parts = array_map( 'trim', $parts ); - - // Remove empty parts. - $parts = array_filter( $parts ); - - // Split parts on ':' characters. - $parts = static::deconstruct_selector_array_on_character( $parts, ':' ); - - return $parts; - } - - /** - * Expands and re-structures an array based on a character. - * - * @static - * - * @param array $array The array to expand. - * @param string $character The character to expand on. - * - * @return array The expanded array. - */ - public static function deconstruct_selector_array_on_character( $array, $character ) { - $new_array = array(); - foreach ( $array as $value ) { - if ( strpos( $value, $character ) === false ) { - $new_array[] = $value; - continue; - } - - // Use "SPLIT_CHARACTER" as a delimiter. - $value = str_replace( $character, "SPLIT_CHARACTER$character", $value ); - - // Pseudo-selectors are a special case. - if ( ':' === $character ) { - $value = str_replace( ':', '&:', $value ); - } - - $value = explode( 'SPLIT_CHARACTER', $value ); - $new_array = array_merge( $new_array, $value ); - } - return $new_array; - } } From 7c48c5ef28be2283b25480eaeac714d5dad26e3a Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Wed, 13 Jul 2022 11:13:01 +0300 Subject: [PATCH 06/17] escape the selector --- packages/style-engine/class-wp-style-engine-css-rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index 53eb4594188f52..902e7ac6a8dc3b 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -54,7 +54,7 @@ public function set_selector( $selector ) { if ( empty( $selector ) ) { return; } - $this->selector = $selector; + $this->selector = esc_html( $selector ); } /** From ca30f7e7811d3e0593f43cb34d45852a5d8d59f6 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Wed, 13 Jul 2022 11:29:45 +0300 Subject: [PATCH 07/17] Allow for multiple named instances of the store --- .../class-wp-style-engine-css-rules-store.php | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index d0a8b1a18873c9..3ac4ef4648d787 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -19,33 +19,49 @@ class WP_Style_Engine_CSS_Rules_Store { /** - * An array of CSS Rules objects. + * An array of named WP_Style_Engine_CSS_Rules_Store objects. * * @static * + * @var WP_Style_Engine_CSS_Rules_Store[] + */ + protected static $stores = array(); + + /** + * An array of CSS Rules objects assigned to the store. + * * @var WP_Style_Engine_CSS_Rule[] */ - protected static $rules = array(); + protected $rules = array(); /** - * Get an array of all rules. + * Get an instance of the store. * - * @static + * @param string $store_name The name of the store. + * + * @return WP_Style_Engine_CSS_Rules_Store + */ + public static function get_store( $store_name ) { + if ( ! isset( static::$stores[ $store_name ] ) ) { + static::$stores[ $store_name ] = new WP_Style_Engine_CSS_Rules_Store( $store_name ); + } + return static::$stores[ $store_name ]; + } + /** + * Get an array of all rules. * * @return WP_Style_Engine_CSS_Rule[] */ - public static function get_all_rules() { - return static::$rules; + public function get_all_rules() { + return $this->rules; } /** * Get a WP_Style_Engine_CSS_Rule object by its selector. * - * @static - * * @param string $selector The CSS selector. */ - public static function get_rule( $selector ) { + public function get_rule( $selector ) { $selector = trim( $selector ); @@ -55,10 +71,10 @@ public static function get_rule( $selector ) { } // Create the rule if it doesn't exist. - if ( empty( static::$rules[ $selector ] ) ) { - static::$rules[ $selector ] = new WP_Style_Engine_CSS_Rule( $selector ); + if ( empty( $this->rules[ $selector ] ) ) { + $this->rules[ $selector ] = new WP_Style_Engine_CSS_Rule( $selector ); } - return static::$rules[ $selector ]; + return $this->rules[ $selector ]; } } From 9de86d72129e636e08d46f76f3faf0a805968742 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 14 Jul 2022 10:18:15 +0300 Subject: [PATCH 08/17] Use a single declarations object per rule --- .../class-wp-style-engine-css-rule.php | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index 902e7ac6a8dc3b..ad3a245df75654 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -28,17 +28,18 @@ class WP_Style_Engine_CSS_Rule { /** * The selector declarations. * - * Contains an array of WP_Style_Engine_CSS_Declarations objects. + * Contains a WP_Style_Engine_CSS_Declarations object. * - * @var WP_Style_Engine_CSS_Declarations[] + * @var WP_Style_Engine_CSS_Declarations */ - protected $declarations = array(); + protected $declarations; /** * Constructor * - * @param string $selector The CSS selector. - * @param WP_Style_Engine_CSS_Declarations[] $declarations An array of WP_Style_Engine_CSS_Declarations objects. + * @param string $selector The CSS selector. + * @param array|WP_Style_Engine_CSS_Declarations $declarations An array of declarations (property => value pairs), + * or a WP_Style_Engine_CSS_Declarations object. */ public function __construct( $selector = '', $declarations = array() ) { $this->set_selector( $selector ); @@ -60,13 +61,28 @@ public function set_selector( $selector ) { /** * Set the declarations. * - * @param WP_Style_Engine_CSS_Declarations[] $declarations An array of WP_Style_Engine_CSS_Declarations objects. + * @param array|WP_Style_Engine_CSS_Declarations $declarations An array of declarations (property => value pairs), + * or a WP_Style_Engine_CSS_Declarations object. */ public function set_declarations( $declarations ) { - if ( $declarations instanceof WP_Style_Engine_CSS_Declarations ) { - $declarations = array( $declarations ); + $is_declarations_object = ! is_array( $declarations ); + $declarations_array = $is_declarations_object ? $declarations->get_declarations() : $declarations; + + if ( null === $this->declarations && $is_declarations_object ) { + $this->declarations = $declarations; + } elseif ( null === $this->declarations ) { + $this->declarations = new WP_Style_Engine_CSS_Declarations( $declarations_array ); } - $this->declarations = array_merge( $this->declarations, $declarations ); + $this->declarations->add_declarations( $declarations_array ); + } + + /** + * Get the declarations object. + * + * @return WP_Style_Engine_CSS_Declarations + */ + public function get_declarations() { + return $this->declarations; } /** @@ -84,11 +100,6 @@ public function get_selector() { * @return string */ public function get_css() { - $css = $this->get_selector() . ' {'; - foreach ( $this->declarations as $declaration ) { - $css .= $declaration->get_declarations_string(); - } - $css .= '}'; - return $css; + return $this->get_selector() . ' {' . $this->declarations->get_declarations_string() . '}'; } } From 59dbf675612ed049c9d69d366f91527dd1b4307b Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 14 Jul 2022 10:20:25 +0300 Subject: [PATCH 09/17] Allow chaining methods --- packages/style-engine/class-wp-style-engine-css-rule.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index ad3a245df75654..93be7fceea2775 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -50,12 +50,16 @@ public function __construct( $selector = '', $declarations = array() ) { * Set the selector. * * @param string $selector The CSS selector. + * + * @return WP_Style_Engine_CSS_Rule Returns the object to allow chaining of methods. */ public function set_selector( $selector ) { if ( empty( $selector ) ) { return; } $this->selector = esc_html( $selector ); + + return $this; } /** @@ -63,6 +67,8 @@ public function set_selector( $selector ) { * * @param array|WP_Style_Engine_CSS_Declarations $declarations An array of declarations (property => value pairs), * or a WP_Style_Engine_CSS_Declarations object. + * + * @return WP_Style_Engine_CSS_Rule Returns the object to allow chaining of methods. */ public function set_declarations( $declarations ) { $is_declarations_object = ! is_array( $declarations ); @@ -74,6 +80,8 @@ public function set_declarations( $declarations ) { $this->declarations = new WP_Style_Engine_CSS_Declarations( $declarations_array ); } $this->declarations->add_declarations( $declarations_array ); + + return $this; } /** From 94a141a6f54fc47bc974213dacde52cecdb38251 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 14 Jul 2022 10:28:46 +0300 Subject: [PATCH 10/17] Remove esc_html --- packages/style-engine/class-wp-style-engine-css-rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index 93be7fceea2775..e10c07183d21ae 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -57,7 +57,7 @@ public function set_selector( $selector ) { if ( empty( $selector ) ) { return; } - $this->selector = esc_html( $selector ); + $this->selector = $selector; return $this; } From be06402b076fbcbf0fbc4aeb3cb9dc35b3df8cda Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 14 Jul 2022 13:12:54 +0300 Subject: [PATCH 11/17] Add a default value for store_name --- packages/style-engine/class-wp-style-engine-css-rules-store.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index 3ac4ef4648d787..2d95fbaee161b2 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -41,7 +41,7 @@ class WP_Style_Engine_CSS_Rules_Store { * * @return WP_Style_Engine_CSS_Rules_Store */ - public static function get_store( $store_name ) { + public static function get_store( $store_name = 'default' ) { if ( ! isset( static::$stores[ $store_name ] ) ) { static::$stores[ $store_name ] = new WP_Style_Engine_CSS_Rules_Store( $store_name ); } From d4a21048f23ee37054710370fd12f7d8d29daa15 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 14 Jul 2022 13:18:05 +0300 Subject: [PATCH 12/17] use static() --- packages/style-engine/class-wp-style-engine-css-rules-store.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index 2d95fbaee161b2..bfc7fba9ecd71f 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -43,7 +43,7 @@ class WP_Style_Engine_CSS_Rules_Store { */ public static function get_store( $store_name = 'default' ) { if ( ! isset( static::$stores[ $store_name ] ) ) { - static::$stores[ $store_name ] = new WP_Style_Engine_CSS_Rules_Store( $store_name ); + static::$stores[ $store_name ] = new static( $store_name ); } return static::$stores[ $store_name ]; } From 4104e21d01d3056f341a18debde64736da645793 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 15 Jul 2022 11:30:54 +1000 Subject: [PATCH 13/17] Adding tests --- .../class-wp-style-engine-css-rule-test.php | 96 +++++++++++++++ ...s-wp-style-engine-css-rules-store-test.php | 115 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php create mode 100644 packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php diff --git a/packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php b/packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php new file mode 100644 index 00000000000000..9a42ade4acb16f --- /dev/null +++ b/packages/style-engine/phpunit/class-wp-style-engine-css-rule-test.php @@ -0,0 +1,96 @@ + '10px', + 'font-size' => '2rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule( $selector, $css_declarations ); + + $this->assertSame( $selector, $css_rule->get_selector() ); + + $expected = "$selector {{$css_declarations->get_declarations_string()}}"; + $this->assertSame( $expected, $css_rule->get_css() ); + } + + /** + * Test dedupe declaration properties. + */ + public function test_dedupe_properties_in_rules() { + $selector = '.taggart'; + $first_declaration = array( + 'font-size' => '2rem', + ); + $overwrite_first_declaration = array( + 'font-size' => '4px', + ); + $css_rule = new WP_Style_Engine_CSS_Rule( $selector, $first_declaration ); + $css_rule->set_declarations( new WP_Style_Engine_CSS_Declarations( $overwrite_first_declaration ) ); + + $expected = '.taggart {font-size: 4px;}'; + $this->assertSame( $expected, $css_rule->get_css() ); + } + + /** + * Should set selector and rules on instantiation. + */ + public function test_set_declarations() { + // Declarations using a WP_Style_Engine_CSS_Declarations object. + $some_css_declarations = new WP_Style_Engine_CSS_Declarations( array( 'margin-top' => '10px' ) ); + // Declarations using a property => value array. + $some_more_css_declarations = array( 'font-size' => '1rem' ); + $css_rule = new WP_Style_Engine_CSS_Rule( '.hill-street-blues', $some_css_declarations ); + $css_rule->set_declarations( $some_more_css_declarations ); + + $expected = '.hill-street-blues {margin-top: 10px; font-size: 1rem;}'; + $this->assertSame( $expected, $css_rule->get_css() ); + } + + /** + * Should set selector and rules on instantiation. + */ + public function test_set_selector() { + $selector = '.taggart'; + $css_rule = new WP_Style_Engine_CSS_Rule( $selector ); + + $this->assertSame( $selector, $css_rule->get_selector() ); + + $css_rule->set_selector( '.law-and-order' ); + + $this->assertSame( '.law-and-order', $css_rule->get_selector() ); + } + + /** + * Should set selector and rules on instantiation. + */ + public function test_get_css() { + $selector = '.chips'; + $input_declarations = array( + 'margin-top' => '10px', + 'font-size' => '2rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule( $selector, $css_declarations ); + $expected = "$selector {{$css_declarations->get_declarations_string()}}"; + + $this->assertSame( $expected, $css_rule->get_css() ); + } +} diff --git a/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php b/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php new file mode 100644 index 00000000000000..390700a9e5ac48 --- /dev/null +++ b/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php @@ -0,0 +1,115 @@ +assertInstanceOf( 'WP_Style_Engine_CSS_Rules_Store', $new_pancakes_store ); + } + + /** + * Should return previously created store when the same selector key is passed. + */ + public function test_get_store() { + $new_fish_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'fish-n-chips' ); + $selector = '.haddock'; + + $new_fish_store->get_rule( $selector )->get_selector(); + $this->assertEquals( $selector, $new_fish_store->get_rule( $selector )->get_selector() ); + + $the_same_fish_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'fish-n-chips' ); + $this->assertEquals( $selector, $the_same_fish_store->get_rule( $selector )->get_selector() ); + } + + /** + * Should return a stored rule. + */ + public function test_get_rule() { + $new_pie_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'meat-pie' ); + $selector = '.wp-block-sauce a:hover'; + $store_rule = $new_pie_store->get_rule( $selector ); + $expected = "$selector {}"; + $this->assertEquals( $expected, $store_rule->get_css() ); + + $pie_declarations = array( + 'color' => 'brown', + 'border-color' => 'yellow', + 'border-radius' => '10rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $pie_declarations ); + $store_rule->set_declarations( $css_declarations ); + + $store_rule = $new_pie_store->get_rule( $selector ); + $expected = "$selector {{$css_declarations->get_declarations_string()}}"; + $this->assertEquals( $expected, $store_rule->get_css() ); + } + + /** + * Should return all stored rules. + */ + public function test_get_all_rules() { + $new_pizza_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'pizza-with-mozzarella' ); + $selector = '.wp-block-anchovies a:hover'; + $store_rule = $new_pizza_store->get_rule( $selector ); + $expected = array( + $selector => $store_rule, + ); + + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + + $pizza_declarations = array( + 'color' => 'red', + 'border-color' => 'yellow', + 'border-radius' => '10rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $pizza_declarations ); + $store_rule->set_declarations( array( $css_declarations ) ); + + $expected = array( + $selector => $store_rule, + ); + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + + $new_pizza_declarations = array( + 'color' => 'red', + 'border-color' => 'red', + 'font-size' => '10rem', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $new_pizza_declarations ); + $store_rule->set_declarations( array( $css_declarations ) ); + + $expected = array( + $selector => $store_rule, + ); + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + + $new_selector = '.wp-block-mushroom a:hover'; + $newer_pizza_declarations = array( + 'padding' => '100px', + ); + $new_store_rule = $new_pizza_store->get_rule( $new_selector ); + $css_declarations = new WP_Style_Engine_CSS_Declarations( $newer_pizza_declarations ); + $new_store_rule->set_declarations( array( $css_declarations ) ); + + $expected = array( + $selector => $store_rule, + $new_selector => $new_store_rule, + ); + $this->assertEquals( $expected, $new_pizza_store->get_all_rules() ); + } +} From 8deefb84993716664276c5582a50b7a8ee07b364 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Fri, 15 Jul 2022 09:47:33 +0300 Subject: [PATCH 14/17] rename get_rule to add_rule --- .../class-wp-style-engine-css-rules-store.php | 3 ++- ...lass-wp-style-engine-css-rules-store-test.php | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index bfc7fba9ecd71f..2e76fdb929330b 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -58,10 +58,11 @@ public function get_all_rules() { /** * Get a WP_Style_Engine_CSS_Rule object by its selector. + * If the rule does not exist, it will be created. * * @param string $selector The CSS selector. */ - public function get_rule( $selector ) { + public function add_rule( $selector ) { $selector = trim( $selector ); diff --git a/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php b/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php index 390700a9e5ac48..b06c4d11d66f30 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-css-rules-store-test.php @@ -29,20 +29,20 @@ public function test_get_store() { $new_fish_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'fish-n-chips' ); $selector = '.haddock'; - $new_fish_store->get_rule( $selector )->get_selector(); - $this->assertEquals( $selector, $new_fish_store->get_rule( $selector )->get_selector() ); + $new_fish_store->add_rule( $selector )->get_selector(); + $this->assertEquals( $selector, $new_fish_store->add_rule( $selector )->get_selector() ); $the_same_fish_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'fish-n-chips' ); - $this->assertEquals( $selector, $the_same_fish_store->get_rule( $selector )->get_selector() ); + $this->assertEquals( $selector, $the_same_fish_store->add_rule( $selector )->get_selector() ); } /** * Should return a stored rule. */ - public function test_get_rule() { + public function test_add_rule() { $new_pie_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'meat-pie' ); $selector = '.wp-block-sauce a:hover'; - $store_rule = $new_pie_store->get_rule( $selector ); + $store_rule = $new_pie_store->add_rule( $selector ); $expected = "$selector {}"; $this->assertEquals( $expected, $store_rule->get_css() ); @@ -54,7 +54,7 @@ public function test_get_rule() { $css_declarations = new WP_Style_Engine_CSS_Declarations( $pie_declarations ); $store_rule->set_declarations( $css_declarations ); - $store_rule = $new_pie_store->get_rule( $selector ); + $store_rule = $new_pie_store->add_rule( $selector ); $expected = "$selector {{$css_declarations->get_declarations_string()}}"; $this->assertEquals( $expected, $store_rule->get_css() ); } @@ -65,7 +65,7 @@ public function test_get_rule() { public function test_get_all_rules() { $new_pizza_store = WP_Style_Engine_CSS_Rules_Store::get_store( 'pizza-with-mozzarella' ); $selector = '.wp-block-anchovies a:hover'; - $store_rule = $new_pizza_store->get_rule( $selector ); + $store_rule = $new_pizza_store->add_rule( $selector ); $expected = array( $selector => $store_rule, ); @@ -102,7 +102,7 @@ public function test_get_all_rules() { $newer_pizza_declarations = array( 'padding' => '100px', ); - $new_store_rule = $new_pizza_store->get_rule( $new_selector ); + $new_store_rule = $new_pizza_store->add_rule( $new_selector ); $css_declarations = new WP_Style_Engine_CSS_Declarations( $newer_pizza_declarations ); $new_store_rule->set_declarations( array( $css_declarations ) ); From c3bfa2f3beb204ee82081ddd1cc61a487f2bbf54 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Fri, 15 Jul 2022 09:47:43 +0300 Subject: [PATCH 15/17] add a remove_rule method --- .../class-wp-style-engine-css-rules-store.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index 2e76fdb929330b..28f0f78ac4f1c7 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -78,4 +78,15 @@ public function add_rule( $selector ) { return $this->rules[ $selector ]; } + + /** + * Remove a selector from the store. + * + * @param string $selector The CSS selector. + * + * @return void + */ + public function remove_rule( $selector ) { + unset( $this->rules[ $selector ] ); + } } From a5f46e94e7c2d317e0cd2c37223738627bf26be9 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Fri, 15 Jul 2022 10:21:16 +0300 Subject: [PATCH 16/17] minor performance improvement --- packages/style-engine/class-wp-style-engine-css-rule.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index e10c07183d21ae..855dd7ecc4b712 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -76,7 +76,9 @@ public function set_declarations( $declarations ) { if ( null === $this->declarations && $is_declarations_object ) { $this->declarations = $declarations; - } elseif ( null === $this->declarations ) { + return $this; + } + if ( null === $this->declarations ) { $this->declarations = new WP_Style_Engine_CSS_Declarations( $declarations_array ); } $this->declarations->add_declarations( $declarations_array ); From f85c3bd15e5a3324e183b2d53c95959417985749 Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Fri, 15 Jul 2022 10:33:35 +0300 Subject: [PATCH 17/17] cleanup & added a return doc --- .../style-engine/class-wp-style-engine-css-rules-store.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rules-store.php b/packages/style-engine/class-wp-style-engine-css-rules-store.php index 28f0f78ac4f1c7..1dc4da265accc7 100644 --- a/packages/style-engine/class-wp-style-engine-css-rules-store.php +++ b/packages/style-engine/class-wp-style-engine-css-rules-store.php @@ -43,7 +43,7 @@ class WP_Style_Engine_CSS_Rules_Store { */ public static function get_store( $store_name = 'default' ) { if ( ! isset( static::$stores[ $store_name ] ) ) { - static::$stores[ $store_name ] = new static( $store_name ); + static::$stores[ $store_name ] = new static(); } return static::$stores[ $store_name ]; } @@ -61,6 +61,8 @@ public function get_all_rules() { * If the rule does not exist, it will be created. * * @param string $selector The CSS selector. + * + * @return WP_Style_Engine_CSS_Rule|null Returns a WP_Style_Engine_CSS_Rule object, or null if the selector is empty. */ public function add_rule( $selector ) {