/wp-content/plugins/elementor/includes/stylesheet.php

https://gitlab.com/campus-academy/krowkaramel · PHP · 414 lines · 162 code · 71 blank · 181 comment · 23 complexity · ea20f042df624c5230bf682a02caa736 MD5 · raw file

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