class-wp-style-engine-processor.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. /**
  3. * WP_Style_Engine_Processor
  4. *
  5. * Compiles styles from stores or collection of CSS rules.
  6. *
  7. * @package WordPress
  8. * @subpackage StyleEngine
  9. * @since 6.1.0
  10. */
  11. /**
  12. * Class WP_Style_Engine_Processor.
  13. *
  14. * Compiles styles from stores or collection of CSS rules.
  15. *
  16. * @since 6.1.0
  17. */
  18. #[AllowDynamicProperties]
  19. class WP_Style_Engine_Processor {
  20. /**
  21. * A collection of Style Engine Store objects.
  22. *
  23. * @since 6.1.0
  24. * @var WP_Style_Engine_CSS_Rules_Store[]
  25. */
  26. protected $stores = array();
  27. /**
  28. * The set of CSS rules that this processor will work on.
  29. *
  30. * @since 6.1.0
  31. * @var WP_Style_Engine_CSS_Rule[]
  32. */
  33. protected $css_rules = array();
  34. /**
  35. * Adds a store to the processor.
  36. *
  37. * @since 6.1.0
  38. *
  39. * @param WP_Style_Engine_CSS_Rules_Store $store The store to add.
  40. *
  41. * @return WP_Style_Engine_Processor Returns the object to allow chaining methods.
  42. */
  43. public function add_store( $store ) {
  44. if ( ! $store instanceof WP_Style_Engine_CSS_Rules_Store ) {
  45. _doing_it_wrong(
  46. __METHOD__,
  47. __( '$store must be an instance of WP_Style_Engine_CSS_Rules_Store' ),
  48. '6.1.0'
  49. );
  50. return $this;
  51. }
  52. $this->stores[ $store->get_name() ] = $store;
  53. return $this;
  54. }
  55. /**
  56. * Adds rules to be processed.
  57. *
  58. * @since 6.1.0
  59. *
  60. * @param WP_Style_Engine_CSS_Rule|WP_Style_Engine_CSS_Rule[] $css_rules A single, or an array of, WP_Style_Engine_CSS_Rule objects from a store or otherwise.
  61. *
  62. * @return WP_Style_Engine_Processor Returns the object to allow chaining methods.
  63. */
  64. public function add_rules( $css_rules ) {
  65. if ( ! is_array( $css_rules ) ) {
  66. $css_rules = array( $css_rules );
  67. }
  68. foreach ( $css_rules as $rule ) {
  69. $selector = $rule->get_selector();
  70. if ( isset( $this->css_rules[ $selector ] ) ) {
  71. $this->css_rules[ $selector ]->add_declarations( $rule->get_declarations() );
  72. continue;
  73. }
  74. $this->css_rules[ $rule->get_selector() ] = $rule;
  75. }
  76. return $this;
  77. }
  78. /**
  79. * Gets the CSS rules as a string.
  80. *
  81. * @since 6.1.0
  82. *
  83. * @param array $options {
  84. * Optional. An array of options. Default empty array.
  85. *
  86. * @type bool $optimize Whether to optimize the CSS output, e.g., combine rules. Default is `false`.
  87. * @type bool $prettify Whether to add new lines and indents to output. Default is the test of whether the global constant `SCRIPT_DEBUG` is defined.
  88. * }
  89. *
  90. * @return string The computed CSS.
  91. */
  92. public function get_css( $options = array() ) {
  93. $defaults = array(
  94. 'optimize' => true,
  95. 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG,
  96. );
  97. $options = wp_parse_args( $options, $defaults );
  98. // If we have stores, get the rules from them.
  99. foreach ( $this->stores as $store ) {
  100. $this->add_rules( $store->get_all_rules() );
  101. }
  102. // Combine CSS selectors that have identical declarations.
  103. if ( true === $options['optimize'] ) {
  104. $this->combine_rules_selectors();
  105. }
  106. // Build the CSS.
  107. $css = '';
  108. foreach ( $this->css_rules as $rule ) {
  109. $css .= $rule->get_css( $options['prettify'] );
  110. $css .= $options['prettify'] ? "\n" : '';
  111. }
  112. return $css;
  113. }
  114. /**
  115. * Combines selectors from the rules store when they have the same styles.
  116. *
  117. * @since 6.1.0
  118. *
  119. * @return void
  120. */
  121. private function combine_rules_selectors() {
  122. // Build an array of selectors along with the JSON-ified styles to make comparisons easier.
  123. $selectors_json = array();
  124. foreach ( $this->css_rules as $rule ) {
  125. $declarations = $rule->get_declarations()->get_declarations();
  126. ksort( $declarations );
  127. $selectors_json[ $rule->get_selector() ] = wp_json_encode( $declarations );
  128. }
  129. // Combine selectors that have the same styles.
  130. foreach ( $selectors_json as $selector => $json ) {
  131. // Get selectors that use the same styles.
  132. $duplicates = array_keys( $selectors_json, $json, true );
  133. // Skip if there are no duplicates.
  134. if ( 1 >= count( $duplicates ) ) {
  135. continue;
  136. }
  137. $declarations = $this->css_rules[ $selector ]->get_declarations();
  138. foreach ( $duplicates as $key ) {
  139. // Unset the duplicates from the $selectors_json array to avoid looping through them as well.
  140. unset( $selectors_json[ $key ] );
  141. // Remove the rules from the rules collection.
  142. unset( $this->css_rules[ $key ] );
  143. }
  144. // Create a new rule with the combined selectors.
  145. $duplicate_selectors = implode( ',', $duplicates );
  146. $this->css_rules[ $duplicate_selectors ] = new WP_Style_Engine_CSS_Rule( $duplicate_selectors, $declarations );
  147. }
  148. }
  149. }