stylesheet.php (9122B)
1 <?php 2 namespace Elementor; 3 4 if ( ! defined( 'ABSPATH' ) ) { 5 exit; // Exit if accessed directly. 6 } 7 8 /** 9 * Elementor stylesheet. 10 * 11 * Elementor stylesheet handler class responsible for setting up CSS rules and 12 * properties, and all the CSS `@media` rule with supported viewport width. 13 * 14 * @since 1.0.0 15 */ 16 class Stylesheet { 17 18 /** 19 * CSS Rules. 20 * 21 * Holds the list of CSS rules. 22 * 23 * @since 1.0.0 24 * @access private 25 * 26 * @var array A list of CSS rules. 27 */ 28 private $rules = []; 29 30 /** 31 * Devices. 32 * 33 * Holds the list of devices. 34 * 35 * @since 1.0.0 36 * @access private 37 * 38 * @var array A list of devices. 39 */ 40 private $devices = []; 41 42 /** 43 * Raw CSS. 44 * 45 * Holds the raw CSS. 46 * 47 * @since 1.0.0 48 * @access private 49 * 50 * @var array The raw CSS. 51 */ 52 private $raw = []; 53 54 /** 55 * Parse CSS rules. 56 * 57 * Goes over the list of CSS rules and generates the final CSS. 58 * 59 * @since 1.0.0 60 * @access public 61 * @static 62 * 63 * @param array $rules CSS rules. 64 * 65 * @return string Parsed rules. 66 */ 67 public static function parse_rules( array $rules ) { 68 $parsed_rules = ''; 69 70 foreach ( $rules as $selector => $properties ) { 71 $selector_content = self::parse_properties( $properties ); 72 73 if ( $selector_content ) { 74 $parsed_rules .= $selector . '{' . $selector_content . '}'; 75 } 76 } 77 78 return $parsed_rules; 79 } 80 81 /** 82 * Parse CSS properties. 83 * 84 * Goes over the selector properties and generates the CSS of the selector. 85 * 86 * @since 1.0.0 87 * @access public 88 * @static 89 * 90 * @param array $properties CSS properties. 91 * 92 * @return string Parsed properties. 93 */ 94 public static function parse_properties( array $properties ) { 95 $parsed_properties = ''; 96 97 foreach ( $properties as $property_key => $property_value ) { 98 if ( '' !== $property_value ) { 99 $parsed_properties .= $property_key . ':' . $property_value . ';'; 100 } 101 } 102 103 return $parsed_properties; 104 } 105 106 /** 107 * Add device. 108 * 109 * Add a new device to the devices list. 110 * 111 * @since 1.0.0 112 * @access public 113 * 114 * @param string $device_name Device name. 115 * @param string $device_max_point Device maximum point. 116 * 117 * @return Stylesheet The current stylesheet class instance. 118 */ 119 public function add_device( $device_name, $device_max_point ) { 120 $this->devices[ $device_name ] = $device_max_point; 121 122 asort( $this->devices ); 123 124 return $this; 125 } 126 127 /** 128 * Add rules. 129 * 130 * Add a new CSS rule to the rules list. 131 * 132 * @since 1.0.0 133 * @access public 134 * 135 * @param string $selector CSS selector. 136 * @param array|string $style_rules Optional. Style rules. Default is `null`. 137 * @param array $query Optional. Media query. Default is `null`. 138 * 139 * @return Stylesheet The current stylesheet class instance. 140 */ 141 public function add_rules( $selector, $style_rules = null, array $query = null ) { 142 $query_hash = 'all'; 143 144 if ( $query ) { 145 $query_hash = $this->query_to_hash( $query ); 146 } 147 148 if ( ! isset( $this->rules[ $query_hash ] ) ) { 149 $this->add_query_hash( $query_hash ); 150 } 151 152 if ( null === $style_rules ) { 153 preg_match_all( '/([^\s].+?(?=\{))\{((?s:.)+?(?=}))}/', $selector, $parsed_rules ); 154 155 foreach ( $parsed_rules[1] as $index => $selector ) { 156 $this->add_rules( $selector, $parsed_rules[2][ $index ], $query ); 157 } 158 159 return $this; 160 } 161 162 if ( ! isset( $this->rules[ $query_hash ][ $selector ] ) ) { 163 $this->rules[ $query_hash ][ $selector ] = []; 164 } 165 166 if ( is_string( $style_rules ) ) { 167 $style_rules = array_filter( explode( ';', trim( $style_rules ) ) ); 168 169 $ordered_rules = []; 170 171 foreach ( $style_rules as $rule ) { 172 $property = explode( ':', $rule, 2 ); 173 174 if ( count( $property ) < 2 ) { 175 return $this; 176 } 177 178 $ordered_rules[ trim( $property[0] ) ] = trim( $property[1], ' ;' ); 179 } 180 181 $style_rules = $ordered_rules; 182 } 183 184 $this->rules[ $query_hash ][ $selector ] = array_merge( $this->rules[ $query_hash ][ $selector ], $style_rules ); 185 186 return $this; 187 } 188 189 /** 190 * Add raw CSS. 191 * 192 * Add a raw CSS rule. 193 * 194 * @since 1.0.8 195 * @access public 196 * 197 * @param string $css The raw CSS. 198 * @param string $device Optional. The device. Default is empty. 199 * 200 * @return Stylesheet The current stylesheet class instance. 201 */ 202 public function add_raw_css( $css, $device = '' ) { 203 if ( ! isset( $this->raw[ $device ] ) ) { 204 $this->raw[ $device ] = []; 205 } 206 207 $this->raw[ $device ][] = trim( $css ); 208 209 return $this; 210 } 211 212 /** 213 * Get CSS rules. 214 * 215 * Retrieve the CSS rules. 216 * 217 * @since 1.0.5 218 * @access public 219 * 220 * @param string $device Optional. The device. Default is empty. 221 * @param string $selector Optional. CSS selector. Default is empty. 222 * @param string $property Optional. CSS property. Default is empty. 223 * 224 * @return null|array CSS rules, or `null` if not rules found. 225 */ 226 public function get_rules( $device = null, $selector = null, $property = null ) { 227 if ( ! $device ) { 228 return $this->rules; 229 } 230 231 if ( $property ) { 232 return isset( $this->rules[ $device ][ $selector ][ $property ] ) ? $this->rules[ $device ][ $selector ][ $property ] : null; 233 } 234 235 if ( $selector ) { 236 return isset( $this->rules[ $device ][ $selector ] ) ? $this->rules[ $device ][ $selector ] : null; 237 } 238 239 return isset( $this->rules[ $device ] ) ? $this->rules[ $device ] : null; 240 } 241 242 /** 243 * To string. 244 * 245 * This magic method responsible for parsing the rules into one CSS string. 246 * 247 * @since 1.0.0 248 * @access public 249 * 250 * @return string CSS style. 251 */ 252 public function __toString() { 253 $style_text = ''; 254 255 foreach ( $this->rules as $query_hash => $rule ) { 256 $device_text = self::parse_rules( $rule ); 257 258 if ( 'all' !== $query_hash ) { 259 $device_text = $this->get_query_hash_style_format( $query_hash ) . '{' . $device_text . '}'; 260 } 261 262 $style_text .= $device_text; 263 } 264 265 foreach ( $this->raw as $device_name => $raw ) { 266 $raw = implode( "\n", $raw ); 267 268 if ( $raw && isset( $this->devices[ $device_name ] ) ) { 269 $raw = '@media(max-width: ' . $this->devices[ $device_name ] . 'px){' . $raw . '}'; 270 } 271 272 $style_text .= $raw; 273 } 274 275 return $style_text; 276 } 277 278 /** 279 * Query to hash. 280 * 281 * Turns the media query into a hashed string that represents the query 282 * endpoint in the rules list. 283 * 284 * @since 1.2.0 285 * @access private 286 * 287 * @param array $query CSS media query. 288 * 289 * @return string Hashed string of the query. 290 */ 291 private function query_to_hash( array $query ) { 292 $hash = []; 293 294 foreach ( $query as $endpoint => $value ) { 295 $hash[] = $endpoint . '_' . $value; 296 } 297 298 return implode( '-', $hash ); 299 } 300 301 /** 302 * Hash to query. 303 * 304 * Turns the hashed string to an array that contains the data of the query 305 * endpoint. 306 * 307 * @since 1.2.0 308 * @access private 309 * 310 * @param string $hash Hashed string of the query. 311 * 312 * @return array Media query data. 313 */ 314 private function hash_to_query( $hash ) { 315 $query = []; 316 317 $hash = array_filter( explode( '-', $hash ) ); 318 319 foreach ( $hash as $single_query ) { 320 preg_match( '/(min|max)_(.*)/', $single_query, $query_parts ); 321 322 $end_point = $query_parts[1]; 323 324 $device_name = $query_parts[2]; 325 326 $query[ $end_point ] = 'max' === $end_point ? $this->devices[ $device_name ] : Plugin::$instance->breakpoints->get_device_min_breakpoint( $device_name ); 327 } 328 329 return $query; 330 } 331 332 /** 333 * Add query hash. 334 * 335 * Register new endpoint query and sort the rules the way they should be 336 * displayed in the final stylesheet based on the device and the viewport 337 * width. 338 * 339 * @since 1.2.0 340 * @access private 341 * 342 * @param string $query_hash Hashed string of the query. 343 */ 344 private function add_query_hash( $query_hash ) { 345 $this->rules[ $query_hash ] = []; 346 347 uksort( 348 $this->rules, function( $a, $b ) { 349 if ( 'all' === $a ) { 350 return -1; 351 } 352 353 if ( 'all' === $b ) { 354 return 1; 355 } 356 357 $a_query = $this->hash_to_query( $a ); 358 359 $b_query = $this->hash_to_query( $b ); 360 361 if ( isset( $a_query['min'] ) xor isset( $b_query['min'] ) ) { 362 return 1; 363 } 364 365 if ( isset( $a_query['min'] ) ) { 366 $range = $a_query['min'] - $b_query['min']; 367 368 if ( $range ) { 369 return $range; 370 } 371 372 $a_has_max = isset( $a_query['max'] ); 373 374 if ( $a_has_max xor isset( $b_query['max'] ) ) { 375 return $a_has_max ? 1 : -1; 376 } 377 378 if ( ! $a_has_max ) { 379 return 0; 380 } 381 } 382 383 return $b_query['max'] - $a_query['max']; 384 } 385 ); 386 } 387 388 /** 389 * Get query hash style format. 390 * 391 * Retrieve formated media query rule with the endpoint width settings. 392 * 393 * The method returns the CSS `@media` rule and supported viewport width in 394 * pixels. It can also handel multiple width endpoints. 395 * 396 * @since 1.2.0 397 * @access private 398 * 399 * @param string $query_hash The hash of the query. 400 * 401 * @return string CSS media query. 402 */ 403 private function get_query_hash_style_format( $query_hash ) { 404 $query = $this->hash_to_query( $query_hash ); 405 406 $style_format = []; 407 408 foreach ( $query as $end_point => $value ) { 409 $style_format[] = '(' . $end_point . '-width:' . $value . 'px)'; 410 } 411 412 return '@media' . implode( ' and ', $style_format ); 413 } 414 }