PageRenderTime 88ms CodeModel.GetById 20ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/cssp.php

http://github.com/TurbineCSS/Turbine
PHP | 809 lines | 501 code | 55 blank | 253 comment | 122 complexity | 8a12e17fabeaed228854095fec9729ee MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of Turbine
  4. * http://github.com/SirPepe/Turbine
  5. *
  6. * Copyright Peter Kröner
  7. * Licensed under GNU LGPL 3, see license.txt or http://www.gnu.org/licenses/
  8. */
  9. /**
  10. * Turbine
  11. * CSS Preprocessor
  12. */
  13. class Cssp extends Parser2 {
  14. /**
  15. * Constructor
  16. * @param string $query String of Files to load, separated by ;
  17. * @return void
  18. */
  19. public function __construct($query = NULL){
  20. parent::__construct();
  21. global $browser;
  22. if($query){
  23. $this->load_file($query);
  24. }
  25. }
  26. /**
  27. * compile
  28. * This is where the magic happens
  29. * @return void
  30. */
  31. public function compile(){
  32. $this->apply_aliases();
  33. $this->apply_extenders();
  34. $this->apply_property_expansion();
  35. $this->apply_inheritance();
  36. $this->apply_copying();
  37. $this->apply_constants();
  38. $this->cleanup();
  39. }
  40. /**
  41. * apply_extenders
  42. * Applies selector extender logic
  43. * @return void
  44. */
  45. public function apply_extenders(){
  46. $extenders = array('and', 'numbered', 'generated');
  47. foreach($extenders as $extender){
  48. foreach($this->parsed as $block => $css){
  49. foreach($this->parsed[$block] as $selector => $styles){
  50. $this->apply_extender($extender, $block, $selector, $styles);
  51. }
  52. }
  53. }
  54. }
  55. /**
  56. * apply_extender
  57. * Applies the extenders to selectors
  58. * @param string $extender The extender to apply
  59. * @param string $block The current block
  60. * @param string $selector The selector to process
  61. * @param array $styles The selector's styles
  62. * @return void
  63. */
  64. public function apply_extender($extender, $block, $selector, $styles){
  65. $tokenized = $this->tokenize($selector, array('"' ,"'", ','));
  66. $extended_selector = false;
  67. $new_selector = $selector;
  68. foreach($tokenized as $key => $token){
  69. $temp_selector = '';
  70. switch($extender){
  71. // The &-extender: &.class, &#id or &:selector - ignore "&" if it is the first character in the selector
  72. case 'and':
  73. if(preg_match_all('@(\s+)(\&.|&#|&\:)@', $token, $matches)){
  74. $new_selector = preg_replace('@( \&)@', '', $selector); // Remove the &
  75. $extended_selector = true;
  76. }
  77. break;
  78. // The #foo-(1-10) extender
  79. case 'numbered':
  80. if(preg_match_all('@(.*?)\((\d{1,})-(\d{1,})\)@', $token, $matches)){
  81. // Check if starting value is smaller than ending value - "div.foo(3-1)" i.e. will be ignored
  82. if($matches[2][0] <= $matches[3][0]){
  83. for($i=$matches[2][0]; $i <= $matches[3][0]; $i++){
  84. $temp_selector .= preg_replace('@\((\d{1,})-(\d{1,})\)@', $i, $token) . ", ";
  85. }
  86. }
  87. else{
  88. for($i=$matches[2][0]; $i >= $matches[3][0]; $i--){
  89. $temp_selector .= preg_replace('@\((\d{1,})-(\d{1,})\)@', $i, $token) . ", ";
  90. }
  91. }
  92. $temp_selector = preg_replace('@(, )$@', '', $temp_selector);
  93. $new_selector = str_replace($token, $temp_selector, $new_selector);
  94. $extended_selector = true;
  95. }
  96. break;
  97. // The #foo(bar, baz) extender
  98. case 'generated':
  99. if(preg_match_all('@(.*?)\((.*?)\)($|.*?$)@', $token, $matches)){
  100. $exploded_selectors = explode(',', $matches[2][0]);
  101. foreach($exploded_selectors as $key => $value){
  102. $temp_selector .= preg_replace('@\((.*?)\)@', trim($value), $token) . ", ";
  103. }
  104. $temp_selector = preg_replace('@(, )$@', '', $temp_selector);
  105. $new_selector = str_replace($matches[0][0], $temp_selector, $new_selector);
  106. $extended_selector = true;
  107. }
  108. break;
  109. }
  110. }
  111. // Insert the result
  112. if($extended_selector){
  113. $changed = array();
  114. $changed[$new_selector] = $styles;
  115. $this->insert($changed, $block, $selector);
  116. // Remove old selector
  117. unset($this->parsed[$block][$selector]);
  118. }
  119. }
  120. /**
  121. * apply_constants
  122. * Applies constants to the stylesheet
  123. * @return void
  124. */
  125. public function apply_constants(){
  126. // Apply special constants to all blocks
  127. $this->apply_special_constants();
  128. // Apply constants, if present, from the global block
  129. if(isset($this->parsed['global']['@constants'])){
  130. foreach($this->parsed as $block => $css){
  131. $this->apply_block_constants($this->parsed['global']['@constants'], $block);
  132. }
  133. }
  134. // Apply constants for @media blocks
  135. foreach($this->parsed as $block => $css){
  136. if(isset($this->parsed[$block]['@constants']) && $block != 'global'){
  137. $this->apply_block_constants($this->parsed[$block]['@constants'], $block);
  138. }
  139. }
  140. }
  141. /**
  142. * apply_block_constants
  143. * Applies a set of constants to a specific block of css
  144. * @param array $constants Array of constants
  145. * @param string $block Block key to apply the constants to
  146. * @return void
  147. */
  148. protected function apply_block_constants($constants, $block){
  149. foreach($constants as $constant => $constant_value){
  150. // We will only ever need the last value out of the constant's value array
  151. $constant_value = end($constant_value);
  152. // Apply the value to the elements in the block
  153. foreach($this->parsed[$block] as $selector => $styles){
  154. // Handle everything but @font-face
  155. if($selector != '@font-face'){
  156. foreach($styles as $property => $values){
  157. // Ignore non-css properties
  158. if($property{0} != '_'){
  159. $num_values = count($values);
  160. for($i = 0; $i < $num_values; $i++){
  161. // Get the replacement value
  162. $replacement = $this->get_constant_replacement($block, $constant_value);
  163. // Replace the value with the constant's value
  164. $this->parsed[$block][$selector][$property][$i] = preg_replace('/(\$'.$constant.')\b/', $replacement, $this->parsed[$block][$selector][$property][$i]);
  165. }
  166. }
  167. }
  168. }
  169. // Handle @font-face
  170. else{
  171. foreach($styles as $key => $properties){
  172. foreach($properties as $property => $values){
  173. // Ignore non-css properties
  174. if($property{0} != '_'){
  175. $num_values = count($values);
  176. for($i = 0; $i < $num_values; $i++){
  177. // Get the replacement value
  178. $replacement = $this->get_constant_replacement($block, $constant_value);
  179. // Replace the value with the constant's value
  180. $this->parsed[$block][$selector][$key][$property][$i] = preg_replace('/(\$'.$constant.')\b/', $replacement, $this->parsed[$block][$selector][$key][$property][$i]);
  181. }
  182. }
  183. }
  184. }
  185. }
  186. }
  187. }
  188. }
  189. /**
  190. * get_constant_replacement
  191. * Finds the real replacement for constants that reference other constants
  192. * @param string $block The block where the constant or alias is coming from
  193. * @param string $value The value to find a replacement for
  194. * @return string The Replacement
  195. */
  196. protected function get_constant_replacement($block, $value){
  197. // If not a constant, simply return value
  198. if(!preg_match('/^\$(.*)$/', $value, $matches)){
  199. return $value;
  200. }
  201. // Else search the true replacement
  202. else{
  203. // Search in the given block AND in the global block
  204. $blocks = array('global');
  205. if($block != 'global'){
  206. $blocks[] = $block;
  207. }
  208. foreach($blocks as $block){
  209. if(isset($this->parsed[$block]['@constants'][$matches[1]])){
  210. // We will only ever need the last value out of the constant's value array
  211. return $this->get_constant_replacement($block, end($this->parsed[$block]['@constants'][$matches[1]]));
  212. }
  213. }
  214. }
  215. }
  216. /**
  217. * apply_special_constants
  218. * Applies special constants to all blocks
  219. * @return void
  220. */
  221. protected function apply_special_constants(){
  222. foreach($this->global_constants as $g_constant => $g_value){
  223. foreach($this->parsed as $block => $css){
  224. foreach($this->parsed[$block] as $selector => $styles){
  225. // Handle everything but @font-face
  226. if($selector != '@font-face'){
  227. foreach($styles as $property => $values){
  228. // Ignore non-css properties
  229. if($property{0} != '_'){
  230. $num_values = count($values);
  231. for($i = 0; $i < $num_values; $i++){
  232. $this->parsed[$block][$selector][$property][$i] = preg_replace('/(\$_'.$g_constant.')\b/', $g_value, $this->parsed[$block][$selector][$property][$i]);
  233. }
  234. }
  235. }
  236. }
  237. // Handle @font-face
  238. else{
  239. foreach($styles as $key => $properties){
  240. foreach($properties as $property => $values){
  241. // Ignore non-css properties
  242. if($property{0} != '_'){
  243. $num_values = count($values);
  244. for($i = 0; $i < $num_values; $i++){
  245. $this->parsed[$block][$selector][$key][$property][$i] = preg_replace('/(\$_'.$g_constant.')\b/', $g_value, $this->parsed[$block][$selector][$key][$property][$i]);
  246. }
  247. }
  248. }
  249. }
  250. }
  251. }
  252. }
  253. }
  254. }
  255. /**
  256. * apply_aliases
  257. * Applies selector aliases
  258. * @return void
  259. */
  260. public function apply_aliases(){
  261. // Apply global aliases, if present, to all blocks
  262. if(isset($this->parsed['global']['@aliases'])){
  263. foreach($this->parsed as $block => $css){
  264. $this->apply_block_aliases($this->parsed['global']['@aliases'], $block);
  265. }
  266. }
  267. // Apply aliases for @media blocks
  268. foreach($this->parsed as $block => $css){
  269. if(isset($this->parsed[$block]['@aliases']) && $block != 'global'){
  270. $this->apply_block_aliases($this->parsed[$block]['@aliases'], $block);
  271. }
  272. }
  273. }
  274. /**
  275. * apply_block_aliases
  276. * Applies a set of aliases to a specific block of css
  277. * @param array $aliases Array of aliases
  278. * @param string $block Block key to apply the aliases to
  279. * @return void
  280. */
  281. protected function apply_block_aliases($aliases, $block){
  282. foreach($aliases as $alias => $alias_value){
  283. // We will only ever need the last value out of the constant's value array
  284. $alias_value = end($alias_value);
  285. foreach($this->parsed[$block] as $selector => $styles){
  286. // Replace in selectors: add a new element with the full selector and delete the old one
  287. $newselector = preg_replace('/(\$'.$alias.')\b/', $alias_value, $selector);
  288. if($newselector != $selector){
  289. $elements = array($newselector => $styles);
  290. $this->insert($elements, $block, $selector);
  291. unset($this->parsed[$block][$selector]);
  292. }
  293. // Replace in values
  294. foreach($styles as $property => $value){
  295. // Ignore non-css properties
  296. if($property{0} != '_'){
  297. if(isset($this->parsed[$block][$selector][$property])){
  298. $num_property_values = count($this->parsed[$block][$selector][$property]);
  299. for($i = 0; $i < $num_property_values; $i++){
  300. $matches = array();
  301. if($property == 'extends' && isset($this->parsed[$block][$selector]['extends'][$i])){
  302. $this->parsed[$block][$selector]['extends'][$i] = preg_replace('/(\$'.$alias.')\b/', $alias_value, $this->parsed[$block][$selector]['extends'][$i]);
  303. }
  304. else{
  305. // Ignore @font-face and @import
  306. if($selector != '@font-face' && $selector != '@import'){
  307. if(preg_match('/copy\((.*)[\s]+(.*)\)/', $this->parsed[$block][$selector][$property][$i], $matches)){
  308. $matches[1] = preg_replace('/(\$'.$alias.')\b/', $alias_value, $matches[1]);
  309. $this->parsed[$block][$selector][$property][$i] = 'copy('.$matches[1].' '.$matches[2].')';
  310. }
  311. }
  312. }
  313. }
  314. }
  315. }
  316. }
  317. }
  318. }
  319. }
  320. /**
  321. * apply_inheritance
  322. * Applies inheritance to the stylesheet
  323. * @return void
  324. */
  325. public function apply_inheritance(){
  326. foreach($this->parsed as $block => $css){
  327. foreach($this->parsed[$block] as $selector => $styles){
  328. // Full inheritance
  329. if(isset($this->parsed[$block][$selector]['extends'])){
  330. $num_extends = count($this->parsed[$block][$selector]['extends']);
  331. for($i = 0; $i < $num_extends; $i++){
  332. $not_found = array();
  333. // Parse ancestors
  334. $ancestors = $this->tokenize($this->parsed[$block][$selector]['extends'][$i], array('"', "'", ','));
  335. // List to keep track of all the ancestor's selectors for debugging comment
  336. $ancestors_list = array();
  337. // First merge all the ancestor's rules into one...
  338. $ancestors_rules = array();
  339. foreach($ancestors as $ancestor){
  340. // Find ancestors
  341. $ancestor_keys = $this->find_ancestor_keys($ancestor, $block);
  342. // Add ancestors to the list
  343. $ancestors_list = array_merge($ancestors_list, $ancestor_keys);
  344. // Merge ancestor's rules with own rules
  345. if(!empty($ancestor_keys)){
  346. foreach($ancestor_keys as $ancestor_key){
  347. $ancestors_rules = $this->merge_rules(
  348. $ancestors_rules,
  349. $this->parsed[$block][$ancestor_key],
  350. array(),
  351. true,
  352. $this->array_get_previous($this->parsed[$block][$selector], 'extends', true)
  353. );
  354. }
  355. }
  356. // Otherwise collect the ancestor for error reporting
  357. else{
  358. $not_found[] = $ancestor;
  359. }
  360. }
  361. // ... then merge the combined ancestor's rules into $parsed
  362. $this->parsed[$block][$selector] = $this->merge_rules(
  363. $this->parsed[$block][$selector],
  364. $ancestors_rules,
  365. array(),
  366. false,
  367. $this->array_get_previous($this->parsed[$block][$selector], 'extends', true)
  368. );
  369. // Report errors for every ancestor that was not found
  370. if(!empty($not_found)){
  371. foreach($not_found as $fail){
  372. $this->report_error($selector.' could not find '.$fail.' to inherit properties from.');
  373. }
  374. }
  375. else{
  376. // Add a comment explaining where the inherited properties came from
  377. CSSP::comment($this->parsed[$block][$selector], null, 'Inherited properties from: "'.implode('", "', $ancestors_list).'"');
  378. }
  379. }
  380. // Unset the extends property
  381. unset($this->parsed[$block][$selector]['extends']);
  382. }
  383. }
  384. }
  385. }
  386. /**
  387. * apply_copying
  388. * Applies property copying to the stylesheet
  389. * @return void
  390. */
  391. public function apply_copying(){
  392. foreach($this->parsed as $block => $css){
  393. foreach($this->parsed[$block] as $selector => $styles){
  394. // Handle everything but @font-face
  395. if($selector != '@font-face'){
  396. foreach($styles as $property => $values){
  397. // Ignore non-css properties
  398. if($property{0} != '_'){
  399. $this->apply_copying_values($block, $selector, $property, $values, null);
  400. }
  401. }
  402. }
  403. // Handle @font-face
  404. else{
  405. foreach($styles as $key => $properties){
  406. foreach($properties as $property => $values){
  407. // Ignore non-css properties
  408. if($property{0} != '_'){
  409. $this->apply_copying_values($block, $selector, $property, $values, $key);
  410. }
  411. }
  412. }
  413. }
  414. }
  415. }
  416. }
  417. /**
  418. * apply_copying_values
  419. * Applies copying to a bunch of values
  420. * @param string $block The block of the element being processed
  421. * @param string $selector The selector of the element being processed
  422. * @param string $property The property that is being processed
  423. * @param array $values The values to apply copying to
  424. * @param int $fontfacekey The @font-face index, if any
  425. * @return void
  426. */
  427. private function apply_copying_values($block, $selector, $property, $values, $fontfacekey = NULL){
  428. // Set destination element - if we have a $fontfacekey, target a @font-face element
  429. if($fontfacekey !== NULL){
  430. $dest =& $this->parsed[$block]['@font-face'][$fontfacekey][$property];
  431. }
  432. else{
  433. $dest =& $this->parsed[$block][$selector][$property];
  434. }
  435. // The copy syntax matching regex
  436. $copying_pattern = '/copy\((.*)[\s]+(.*)\)/';
  437. // Loop through the values
  438. $values_num = count($values);
  439. for($i = 0; $i < $values_num; $i++){
  440. if(preg_match($copying_pattern, $values[$i])){
  441. $found = false;
  442. preg_match_all($copying_pattern, $values[$i], $matches);
  443. // Exact selector matches
  444. if(isset($this->parsed[$block][$matches[1][0]][$matches[2][0]])){
  445. $dest[$i] = $this->get_final_value($this->parsed[$block][$matches[1][0]][$matches[2][0]], $property);
  446. $found = true;
  447. }
  448. // Search for partial selector matches, ie. "#foo" in "#bar, #foo, #blah"
  449. else{
  450. foreach($this->parsed[$block] as $full_selectors => $v){
  451. $tokenized_selectors = $this->tokenize($full_selectors, ',');
  452. if(in_array($matches[1][0], $tokenized_selectors)){
  453. if(isset($this->parsed[$block][$full_selectors][$matches[2][0]])){
  454. $dest[$i] = $this->get_final_value($this->parsed[$block][$full_selectors][$matches[2][0]], $property);
  455. $found = true;
  456. }
  457. }
  458. }
  459. }
  460. // Report error if no source was found
  461. if(!$found){
  462. $this->report_error($selector.' could not find '.$matches[1][0].' to copy '.$matches[2][0].' from.');
  463. }
  464. }
  465. }
  466. }
  467. /**
  468. * apply_property_expansion
  469. * Expands comma-sepperated properties
  470. * @return void
  471. */
  472. public function apply_property_expansion(){
  473. foreach($this->parsed as $block => $css){
  474. foreach($this->parsed[$block] as $selector => $styles){
  475. foreach($styles as $property => $value){
  476. // Ignore non-css properties
  477. if($property{0} != '_'){
  478. // Find possible expandable properties
  479. if(strpos($property, ',') !== false){
  480. $properties = $this->tokenize($property, ',');
  481. if(count($properties) > 1){
  482. // Rebuild the selector's contents with the expanded selectors
  483. $newcontents = array();
  484. foreach($this->parsed[$block][$selector] as $p => $v){
  485. if($p == $property){
  486. foreach($properties as $expanded){
  487. $newcontents[$expanded] = $v;
  488. }
  489. }
  490. else{
  491. $newcontents[$p] = $v;
  492. }
  493. }
  494. // Set the selector's contents to the new array with the expanded selectors
  495. $this->parsed[$block][$selector] = $newcontents;
  496. }
  497. }
  498. }
  499. }
  500. }
  501. }
  502. }
  503. /***
  504. * merge_rules
  505. * Merges sets of possibly conflicting css rules
  506. * @param mixed $old The OLD rules (overridden by the new rules)
  507. * @param mixed $new The NEW rules (override the old rules)
  508. * @param array $exclude A list of properties NOT to merge
  509. * @param array $allow_overwrite Allow new rules to overwrite old ones?
  510. * @param string $after The property in the old rules after which the new rules are to be inserted
  511. * @return mixed $rules The new, merged rules
  512. */
  513. public function merge_rules($old, $new, $exclude = array(), $allow_overwrite = true, $after = ''){
  514. // Create a temporary, cleaned up version of $new
  515. $clean = array();
  516. foreach($new as $property => $values){
  517. // If the property is not excluded or a special property...
  518. if(!in_array($property, $exclude) && !in_array($property, $this->special_properties)){
  519. // ... apply the values one by one...
  520. if(isset($clean[$property])){
  521. if($allow_overwrite){
  522. foreach($values as $value){
  523. $clean[$property][] = $value;
  524. }
  525. }
  526. }
  527. // ... or copy the whole set of values
  528. else{
  529. $clean[$property] = $values;
  530. }
  531. }
  532. }
  533. // Combine $clean and $old - either do a simple merge or insert $clean after $after in $old
  534. if($after && isset($old[$after])){
  535. $rules = array();
  536. foreach($old as $oldproperty => $oldvalues){
  537. $rules[$oldproperty] = $oldvalues;
  538. if($oldproperty == $after){
  539. foreach($new as $newproperty => $newvalues){
  540. $rules[$newproperty] = $newvalues;
  541. }
  542. }
  543. }
  544. }
  545. else{
  546. $rules = array_merge($old, $clean);
  547. }
  548. return $rules;
  549. }
  550. /**
  551. * find_ancestor_keys
  552. * Find selectors matching or partially matching $selector (where $selector can also be a label)
  553. * @param string $selector The selector to search
  554. * @param string $block The block to search in
  555. * @return array $results The matching keys (if any)
  556. */
  557. protected function find_ancestor_keys($selector, $block){
  558. $results = array();
  559. foreach($this->parsed[$block] as $key => $value){
  560. $tokens = $this->tokenize($key, ',');
  561. if(in_array($selector, $tokens) ||
  562. (array_key_exists('_label', $this->parsed[$block][$key]) && $this->get_final_value($this->parsed[$block][$key]['_label'], '_label') == $selector)
  563. ){
  564. $results[] = $key;
  565. }
  566. }
  567. return $results;
  568. }
  569. /**
  570. * cleanup
  571. * Deletes empty elements, templates and cssp-only elements
  572. * @return void
  573. */
  574. public function cleanup(){
  575. // Remove @constants and @aliases blocks
  576. foreach($this->parsed as $block => $css){
  577. if(isset($this->parsed[$block]['@constants'])){
  578. unset($this->parsed[$block]['@constants']);
  579. }
  580. if(isset($this->parsed[$block]['@aliases'])){
  581. unset($this->parsed[$block]['@aliases']);
  582. }
  583. // Remove empty elements, templates and alias ruins
  584. foreach($this->parsed[$block] as $selector => $styles){
  585. if(empty($styles) || $selector == '' || $selector{0} == '?' || $selector{0} == '$'){
  586. unset($this->parsed[$block][$selector]);
  587. }
  588. }
  589. }
  590. }
  591. /**
  592. * insert
  593. * Inserts an element at a specific position in the block
  594. * @param array $elements The elements to insert
  595. * @param string $block The block to insert into
  596. * @param string $before The element after which the new element is inserted
  597. * @param string $after The element before which the new element is inserted
  598. * @return void
  599. */
  600. public function insert($elements, $block, $before = NULL, $after = NULL){
  601. $newblock = array();
  602. // If $before and $after are NULL, insert the new Element at the top
  603. if($before == NULL && $after == NULL){
  604. $newblock = $this->insert_elements($elements, $block, $newblock);
  605. }
  606. // Walk through the whole parsed code
  607. foreach($this->parsed[$block] as $selector => $styles){
  608. // Handle $after
  609. if($elements != null && $after != NULL && $selector == $after){
  610. $newblock = $this->insert_elements($elements, $block, $newblock);
  611. $elements = null;
  612. }
  613. // Insert of the current old element
  614. if(isset($newblock[$selector])){
  615. $newblock[$selector] = $this->merge_rules($this->parsed[$block][$selector], $newblock[$selector]);
  616. }
  617. else{
  618. $newblock[$selector] = $this->parsed[$block][$selector];
  619. }
  620. // Handle $before
  621. if($elements != null && $before != NULL && $selector == $before){
  622. $newblock = $this->insert_elements($elements, $block, $newblock);
  623. $elements = null;
  624. }
  625. }
  626. $this->parsed[$block] = $newblock;
  627. }
  628. /**
  629. * insert_elements
  630. * Merges or appends elements into a new block, preserving old styles from $this->parsed
  631. * @param array $elements The elements to insert
  632. * @param string $block The $this->parsed block to take old information from
  633. * @param array $newblock The new block to insert into
  634. * @return array $newblock
  635. */
  636. private function insert_elements($elements, $block, $newblock){
  637. foreach($elements as $newselector => $newstyles){
  638. if(isset($this->parsed[$block][$newselector])){
  639. $newblock[$newselector] = $this->merge_rules($this->parsed[$block][$newselector], $newstyles);
  640. }
  641. else{
  642. $newblock[$newselector] = $newstyles;
  643. }
  644. }
  645. return $newblock;
  646. }
  647. /**
  648. * insert_properties
  649. * Inserts an array of css rules (property => Array of values) into an element at a specific position
  650. * @param array $rules The css rules to insert
  651. * @param string $block The block where the element $element is to be found
  652. * @param string $element The element to insert the rules into
  653. * @param string $before The property after which the rules are to be inserted
  654. * @param string $after The property before which the rules are to be inserted
  655. * @return void
  656. */
  657. public function insert_properties($rules, $block, $element, $before = NULL, $after = NULL){
  658. foreach($rules as $newproperty => $newvalues){
  659. $this->parsed[$block][$element] = $this->insert_property($this->parsed[$block][$element], $newproperty, $newvalues, $before, $after);
  660. }
  661. }
  662. /**
  663. * insert_property
  664. * Inserts a new property into an array without overwriting any other properties
  665. * @param array $set The array to insert into
  666. * @param string $property The property name
  667. * @param array $values The properties' values
  668. * @return array The set with the new property inserted
  669. */
  670. private function insert_property($set, $property, $values, $before = NULL, $after = NULL){
  671. if($before === NULL && $after === NULL){
  672. return $this->append_property($set, $property, $values);
  673. }
  674. elseif($before != NULL){
  675. return $this->insert_property_before($set, $property, $values, $before);
  676. }
  677. else{
  678. return $this->insert_property_after($set, $property, $values, $after);
  679. }
  680. }
  681. /**
  682. * append_property
  683. * Appends a new property to an array without overwriting any other properties
  684. * @param array $set The array to insert into
  685. * @param string $property The property name
  686. * @param array $values The properties' values
  687. * @return array $set The set with the new property inserted
  688. */
  689. private function append_property($set, $property, $values){
  690. // Take care of legacy plugins that might pass a single value as a string
  691. if(!is_array($values)){
  692. $values = array($values);
  693. }
  694. // Insert the property
  695. if(isset($set[$property])){
  696. $set[$property] = array_merge($set[$property], $values);
  697. }
  698. else{
  699. $set[$property] = $values;
  700. }
  701. return $set;
  702. }
  703. /**
  704. * insert_property_before
  705. * Inserts a new property into an array at a specified position without overwriting any other properties
  706. * @param array $set The array to insert into
  707. * @param string $property The property name
  708. * @param array $values The properties' values
  709. * @param string $where The key before which the new property will be inserted
  710. * @return array $set The set with the new property inserted
  711. */
  712. private function insert_property_before($set, $property, $values, $where){
  713. $new = array();
  714. foreach($set as $key => $vals){
  715. if($key == $where){
  716. // Preserve old values
  717. if(isset($set[$property])){
  718. $values = array_merge($set[$property], $values);
  719. unset($set[$property]); // Remove the old property to prevent duplicats
  720. unset($new[$property]); // Remove the old property to prevent duplicats
  721. }
  722. $new[$property] = $values;
  723. }
  724. $new[$key] = $vals;
  725. }
  726. return $new;
  727. }
  728. /**
  729. * insert_property_after
  730. * Inserts a new property into an array at a specified position without overwriting any other properties
  731. * @param array $set The array to insert into
  732. * @param string $property The property name
  733. * @param array $values The properties' values
  734. * @param string $where The key after which the new property will be inserted
  735. * @return array $set The set with the new property inserted
  736. */
  737. private function insert_property_after($set, $property, $values, $where){
  738. $new = array();
  739. foreach($set as $key => $vals){
  740. $new[$key] = $vals;
  741. if($key == $where){
  742. // Preserve old values
  743. if(isset($set[$property])){
  744. $values = array_merge($set[$property], $values);
  745. unset($set[$property]); // Remove the old property to prevent duplicats
  746. unset($new[$property]); // Remove the old property to prevent duplicats
  747. }
  748. $new[$property] = $values;
  749. }
  750. }
  751. return $new;
  752. }
  753. }
  754. ?>