PluginProbe
MaxButtons – Create buttons / 7.13.1
MaxButtons – Create buttons v7.13.1
6.23 6.24 6.25 6.26 6.26.1 6.27 6.28 6.3 6.4 6.5 6.6 6.7 6.8 6.9 7.0 7.1 7.1.1 7.1.2 7.1.3 7.10 7.11 7.13 7.13.1 7.13.2 7.13.3 All 100 releases
maxbuttons / classes / maxCSSParser.php

maxCSSParser.php in MaxButtons – Create buttons 7.13.1, at classes/maxCSSParser.php

647 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace MaxButtons;
3 defined('ABSPATH') or die('No direct access permitted');
4 /* Class to Parse CSS. Load as array with diffent pseudo-types,
5 ability to add nested and new root blocks
6 parses via scss
7 ability to use complicated css stuff via scss mixins parsing like gradient
8 auto-discovery for -unit field types to set units (like px, or %)
9 auto-discovery of fields via domobj.
10
11 */
12
13 use \Exception as Exception;
14
15 class maxCSSParser
16 {
17 protected $struct = array();
18 protected $domObj = '';
19 protected $pseudo = array("hover","active","responsive");
20
21 protected $data;
22 protected $output_css;
23
24 protected $has_compile_error = false;
25 protected $compile_error;
26
27 protected $inline = array();
28 protected $responsive = array();
29
30 public $anchor_class = '.maxbutton'; // used for matching buttons in parse_part
31
32 // settings
33 protected $elements_ignore_zero = array(
34 'text-shadow-left',
35 'text_shadow-top',
36 'text-shadow-width',
37 'box-shadow-offset-left',
38 'box-shadow-offset-top',
39 'box-shadow-width',
40 'box-shadow-spread',
41 ); // items to ignore if value is zero, otherwise they become unremovable ( where 0 is still something on display)
42
43 protected $important = false;
44
45 // log possible problems and incidents for debugging;
46 protected $parse_log = array();
47
48 function __construct()
49 {
50 //$root[] = array("a" => array("hover","active","responsive"));
51 MB()->load_library('scss');
52 }
53
54 function loadDom($domObj)
55 {
56 $this->domObj = $domObj;
57
58 $root = $domObj->find(0,0);
59 $struct[$root->tag] = array();
60
61 $children = $root->children();
62
63 if (count($children) > 0)
64 $struct[$root->tag] = $this->loadRecursive(array(), $children);
65
66
67 // find the full and complete statement class defining maxbutton. This is needed for proper parsing on parse_part.
68 $anchor_element = $root->find('.maxbutton', 0);
69 if (! is_null($anchor_element))
70 {
71 $anchor_class = str_replace(' ', '.', $anchor_element->class);
72 $this->anchor_class = $anchor_class;
73 }
74
75 $this->struct = $struct;
76
77
78 }
79
80 function loadRecursive($struct, $children)
81 {
82 foreach($children as $domChild)
83 {
84
85 $class = $domChild->class;
86
87 $class = str_replace(" ",".", $class); // combine seperate classes
88 $struct[$class]["tag"] = $domChild->tag;
89
90 $child_children = $domChild->children();
91
92 if (count($child_children) > 0)
93 {
94
95 $struct[$class]["children"] = $this->loadRecursive(array(), $child_children);
96 }
97 }
98
99 return $struct;
100 }
101
102
103 function parse($data)
104 {
105 $this->clear();
106
107 $struct = $this->struct;
108
109 $this->data = $data;
110
111 if (isset($data["settings"])) // room for settings in parser
112 {
113 $settings = $data["settings"];
114 $this->important = (isset($settings["important"])) ? $settings["important"] : false;
115
116 unset($this->data["settings"]);
117 }
118
119 $elements = array_shift($struct); // first element is a 'stub' root.
120
121 if ( is_null($elements) )
122 return;
123
124
125 foreach($elements as $el => $el_data)
126 {
127
128 $this->parse_part($el,$el_data);
129 }
130
131
132 $this->parse_responsive();
133
134 maxUtils::startTime('compile CSS');
135 $css = $this->compile($this->output_css);
136
137 maxUtils::endTime('compile CSS');
138
139
140 return $css;
141
142 }
143
144 // reset output values.
145 protected function clear()
146 {
147 $this->data = '';
148 $this->output_css = '';
149 $this->inline = array();
150 $this->responsive = array();
151 }
152
153 public function compile($css)
154 {
155 $scss = new \Leafo\ScssPhp\Compiler();
156 $scss->setImportPaths(MB()->get_plugin_path() . "assets/scss");
157
158 $minify = get_option("maxbuttons_minify", 1);
159
160 if ($minify == 1)
161 {
162 $scss->setFormatter('\Leafo\ScssPhp\Formatter\Crunched');
163 }
164
165 $compile = " @import '_mixins.scss'; " . $css;
166 //maxUtils::addTime("CSSParser: Compile start ");
167
168 try
169 {
170 $css = $scss->compile($compile);
171 } catch (\Exception $e) {
172 $this->has_compile_error = true;
173 $this->compile_error = $e;
174 $css = $this->output_css;
175 }
176
177 //maxUtils::addTime("CSSParser: Compile end ");
178 return $css;
179 }
180
181 function get_compile_errors()
182 {
183 if ($this->has_compile_error)
184 {
185 return $this->compile_error;
186 }
187 return false;
188 }
189
190 /** Element is the current element that being parsed. El_add is the parent element that should be put before the subpart */
191 function parse_part($element, $el_data, $el_add = '')
192 {
193 maxUtils::addTime("CSSParser: Parse $element ");
194
195
196 $tag = $el_data["tag"];
197 $element_data = $this->findData($this->data, $element);
198
199 // not using scss selectors here since there is trouble w/ the :pseudo selector, which should be put on the maxbutton / a tag.
200 if ($element != '')
201 { $el_add .= " ." . $element;
202
203 }
204
205 if (isset($element_data["responsive"]))
206 {
207 $responsive = $element_data["responsive"]; // doing that at the end
208 unset($element_data["responsive"]);
209
210 $this->responsive[$el_add] = $responsive;
211 }
212
213 foreach($element_data as $pseudo => $values)
214 {
215
216 if ($pseudo != 'normal')
217 {
218 // select the maxbutton case, ending with either space or next class -dot.
219 // Anchor class in default situation should be .maxbutton
220 $anchor_class = $this->anchor_class;
221
222 $count = 0;
223
224 /* If PS Selector replacement doesn't match anchor class selector this probably means the parse is done in a higher level
225 e.g. container level, so no proper will be set. In case 0 count replacement, just put it on current */
226 $ps_selector = preg_replace('/' . $anchor_class . '$|' . $anchor_class . '([.| ])/i',"$anchor_class:$pseudo\$1",$el_add, -1, $count);
227
228
229 if ($count === 0)
230 {
231 $ps_selector = $el_add . ":" . $pseudo;
232 }
233
234
235 $this->output_css .= " $ps_selector { ";
236 }
237 else {
238 $this->output_css .= " $el_add { ";
239 }
240
241 $values = $this->doMixins($values);
242
243 foreach($values as $cssTag => $cssVal)
244 {
245
246 $statement = $this->parse_cssline($values, $cssTag,$cssVal); ///"$cssTag $css_sep $cssVal$unit$css_end ";
247
248 if ($statement)
249 {
250 $this->output_css .= $statement ;
251
252 if (! isset($this->inline[$pseudo][$element])) $this->inline[$pseudo][$element] = '';
253 $this->inline[$pseudo][$element] .= $statement;
254 }
255 }
256
257 $this->output_css .= " } ";
258 }
259 if (isset($el_data["children"]))
260 {
261 foreach($el_data["children"] as $child_id => $child_data)
262 {
263
264 $this->parse_part($child_id, $child_data, $el_add);
265 }
266 }
267
268
269 }
270
271 function parse_cssline($values, $cssTag, $cssVal, $css_end = ';')
272 {
273
274 // unit check - two ways; either unitable items is first or unit declaration.
275 if (isset($values[$cssTag . "_unit"]))
276 {
277 $unit = $values[$cssTag . "_unit"];
278 }
279 elseif(strpos($cssTag, "_unit") !== false)
280 {
281 return false; // no print, should be found under first def.
282 }
283 else $unit = '';
284
285
286 $important = ($this->is_important()) ? " !important" : "";
287 $important = ($cssTag == '@include') ? "" : $important; // mixin's problem, no checking here.
288
289 $css_sep = ($cssTag == '@include') ? $css_sep = '' : ':';
290
291 if ($cssVal == 0 && in_array($cssTag, $this->elements_ignore_zero))
292 return false;
293
294 if($cssVal !== '' && $cssTag !== '')
295 {
296 $statement = "$cssTag $css_sep $cssVal$unit$important$css_end ";
297 return $statement;
298 }
299 return false;
300
301 }
302
303 function parse_responsive()
304 {
305
306 $responsive = $this->responsive;
307 if (! is_array($responsive) || count($responsive) == 0)
308 return;
309
310 $media_queries = maxUtils::get_media_query(2); // query names
311
312 $output = '';
313
314 $query_array = array();
315
316
317 foreach($responsive as $element => $queries)
318 {
319 foreach($queries as $query => $qdata)
320 foreach($qdata as $index => $data)
321 {{
322 $query_array[$query][$index][$element] = $data;
323
324 }}
325 }
326
327
328
329 foreach($query_array as $query => $vdata):
330
331 if ($query == 'custom')
332 {
333
334 // first discover the custom size properties.
335 foreach($vdata as $index => $data):
336 foreach($data as $element => $values):
337
338 if (isset($values["custom_maxwidth"]) || isset($values["custom_minwidth"]) )
339 {
340
341 $minwidth = (isset($values["custom_minwidth"])) ? intval($values["custom_minwidth"]) : -1;
342 $maxwidth = (isset($values["custom_maxwidth"])) ? intval($values["custom_maxwidth"]) : -1;
343
344
345 unset($vdata[$index][$element]["custom_minwidth"]);
346 unset($vdata[$index][$element]["custom_maxwidth"]);
347
348
349 // make it always an integer
350 if ($minwidth == '') $minwidth = 0;
351 if ($maxwidth == '') $maxwidth = 0;
352
353 // if minwidth is not set and maxwidth zero or not set - ignore since it would result in an empty media line.
354 //if ($minwidth <= 0 && $maxwidth <= 0)
355 //{
356 //continue;
357 //}
358
359 if ($minwidth > 0 && $maxwidth > 0)
360 $qdef = "only screen and (min-width: $minwidth" . "px) and (max-width: $maxwidth" . "px)";
361 if ($minwidth >= 0 && $maxwidth <= 0)
362 $qdef = "only screen and (min-width: $minwidth" . "px) ";
363 if ($minwidth <= 0 && $maxwidth > 0)
364 $qdef = "only screen and (max-width: $maxwidth" . "px) ";
365
366 //break;
367
368 }
369 endforeach; //foreach data
370
371 // The problem is that every 'custom' query needs to run with a different qdef unlike other queries.
372
373 $qdata = array($vdata[$index]);
374
375 $output = $this->parse_responsive_definition($output, $qdef, $qdata);
376 endforeach; // foreach vdata
377 }
378 else
379 {
380
381 $qdef = $media_queries[$query];
382 $output = $this->parse_responsive_definition($output, $qdef, $vdata);
383 }
384
385 endforeach;
386
387
388 $this->output_css .= $output;
389 }
390
391 protected function parse_responsive_definition($output, $qdef, $vdata)
392 {
393
394 if (! isset($qdef) || $qdef == '') {
395
396 return; // no definition.
397 }
398
399
400 $output .= "@media ". $qdef . " { ";
401
402
403 foreach($vdata as $index => $data)
404 {
405
406 foreach($data as $element => $values) {
407 //foreach($vdat as $index => $values):
408 $output .= $element . " { ";
409 $css_end = ';';
410
411 // same as parse part, maybe merge in future
412 foreach($values as $cssTag => $cssVal)
413 {
414 // unit check - two ways; either unitable items is first or unit declaration.
415 $statement = $this->parse_cssline($values, $cssTag,$cssVal);
416 if($statement)
417 $output .= $statement;
418
419 }
420
421 $output .= " } ";
422 // endforeach;
423 }
424
425
426 }
427 $output .= " } ";
428
429 //endforeach;
430
431 return $output;
432 }
433
434 private function is_important()
435 {
436
437 if ($this->important == 1)
438 return true;
439 else
440 return false;
441 }
442
443 function findData($data, $el)
444 {
445 $classes = explode(".", $el);
446
447 foreach($data as $part => $values)
448 {
449 if (in_array($part, $classes))
450 {
451 return $data[$part];
452 }
453 }
454 return array();
455 }
456
457 function doMixins($values)
458 {
459 $mixins = array("gradient", "box-shadow", "text-shadow", "keyframes");
460
461 foreach($mixins as $mixin)
462 {
463
464 $results = preg_grep("/^$mixin/i",array_keys($values) );
465 if (count($results) === 0)
466 continue; // no mixins.
467
468 $mixin_array = array();
469 foreach($results as $result)
470 {
471 $mixin_array[$result] = $values[$result];
472 }
473
474 if (count($mixin_array) > 0)
475 {
476 switch($mixin)
477 {
478 case "gradient":
479 $values = $this->mixin_gradient($mixin_array, $values);
480 break;
481 case "box-shadow":
482 $values = $this->mixin_boxshadow($mixin_array, $values);
483 break;
484 case "text-shadow":
485 $values = $this->mixin_textshadow($mixin_array, $values);
486 break;
487 case 'keyframes':
488 $values = $this->mixin_keyframes($mixin_array, $values);
489 break;
490 }
491 }
492
493
494 }
495 return $values;
496 }
497
498 /** Parse the keyframes. Not a real mixin */
499 function mixin_keyframes($results, $values)
500 {
501 $keyframes_name = isset($results['keyframes-name']) ? $results['keyframes-name'] : false;
502 $keyframes = isset($results['keyframes']) ? $results['keyframes'] : false;
503
504 $key_output = ' @keyframes ' . $keyframes_name . ' { '.
505 $keyframes . '}';
506
507 $this->output_css = $key_output . $this->output_css;
508
509 $values = array_diff_key($values, $results);
510 return $values;
511 }
512
513 function mixin_gradient($results, $values)
514 {
515
516 $start = isset($results["gradient-start-color"]) ? $results["gradient-start-color"] : '';
517 $end = isset( $results["gradient-end-color"] ) ? $results["gradient-end-color"] : '';
518 $start_opacity = isset( $results["gradient-start-opacity"] ) ? $results["gradient-start-opacity"] : '';
519 $end_opacity = isset( $results["gradient-end-opacity"] ) ? $results["gradient-end-opacity"] : '';
520 $stop = (isset( $results["gradient-stop"]) && $results["gradient-stop"] != '') ? $results["gradient-stop"] . "%" : '45%';
521 // default to use ( old situation )
522 $use_gradient = (isset($results['gradient-use-gradient']) && $results['gradient-use-gradient'] != '') ? $results['gradient-use-gradient'] : 1;
523
524 $start = maxUtils::hex2rgba($start, $start_opacity);
525 $end = maxUtils::hex2rgba($end, $end_opacity);
526
527 $important = ($this->is_important()) ? "!important" : "";
528 //$values = $this->add_include($values, "linear-gradient($start,$end,$stop,$important)");
529
530 if ($use_gradient == 1)
531 {
532 $values = $this->add_include($values, "linear-gradient($start,$end,$stop,$important)");
533 }
534 else {
535 $values['background-color'] = $start;
536 }
537
538 // remove the non-css keys from the value array ( field names )
539 $values = array_diff_key($values, $results);
540
541 return $values;
542
543 }
544
545 function mixin_boxshadow($results, $values)
546 {
547 $width = isset($results["box-shadow-width"]) ? $results["box-shadow-width"] : 0;
548 $left = isset($results["box-shadow-offset-left"]) ? $results["box-shadow-offset-left"] : 0;
549 $top = isset($results["box-shadow-offset-top"]) ? $results["box-shadow-offset-top"] : 0;
550 $spread = isset($results['box-shadow-spread']) ? $results['box-shadow-spread'] : 0;
551 $color = isset($results["box-shadow-color"]) ? $results["box-shadow-color"] : "rgba(0,0,0,0)";
552
553 $important = ($this->is_important()) ? "!important" : "";
554
555 $values = array_diff_key($values, $results); // always remove these fields from CSS since they are not valid.
556
557 if ($width == 0 && $left == 0 && $top == 0 && $spread == 0)
558 {
559 $values['box-shadow'] = 'none'; // if no box-shadow, prevent it in total
560 return $values;
561 }
562
563 $values = $this->add_include($values, "box-shadow($left, $top, $width, $color,$spread, false, $important) ");
564
565
566 return $values;
567 }
568
569 function mixin_textshadow($results, $values)
570 {
571 $width = isset($results["text-shadow-width"]) ? $results["text-shadow-width"] : 0;
572 $left = isset($results["text-shadow-left"]) ? $results["text-shadow-left"] : 0;
573 $top = isset($results["text-shadow-top"]) ? $results["text-shadow-top"] : 0;
574 $color = isset($results["text-shadow-color"]) ? $results["text-shadow-color"] : rgba(0,0,0,0);
575 $important = ($this->is_important()) ? "!important" : "";
576
577 if ($width == 0 && $left == 0 && $top == 0)
578 {
579 $values = array_diff_key($values, $results); // remove them from the values, prevent incorrect output.
580 return $values;
581 }
582
583 $values = $this->add_include($values, "text-shadow ($left,$top,$width,$color $important)");
584 $values = array_diff_key($values, $results);
585
586 return $values;
587 }
588
589 private function add_include($values, $include)
590 {
591 if (isset($values["@include"]))
592 $values["@include"] .= "; @include " . $include;
593 else
594 $values["@include"] = $include;
595 return $values;
596 }
597
598 function outputInline($domObj, $pseudo = 'normal')
599 {
600 $domObj = $domObj->load($domObj->save());
601
602 $inline = $this->inline;
603
604
605 // ISSUE #43 Sometimes this breaks
606 if (! isset($inline[$pseudo]))
607 return $domObj;
608
609 $elements = array_keys($inline[$pseudo]);
610 if ($pseudo != 'normal') // gather all elements
611 $elements = array_merge($elements, array_keys($inline["normal"]));
612
613 foreach($elements as $element )
614 {
615 $styles = isset($inline[$pseudo][$element]) ? $inline[$pseudo][$element] : '';
616
617 if ($pseudo != 'normal') // parse all possible missing styles from pseudo el.
618 $normstyle = $this->compile($inline['normal'][$element]);
619
620 $normstyle = '';
621 if ($pseudo != 'normal') // parse all possible missing styles from pseudo el.
622 $normstyle = $this->compile($inline['normal'][$element]);
623
624 maxUtils::addTime("CSSParser: Parse inline done");
625
626 $styles = $normstyle . $this->compile($styles);
627
628 $element = trim(str_replace("."," ", $element)); // molten css class, seperator.
629
630 $el = $domObj->find('[class*="' . $element . '"]', 0);
631
632 $el->style = $styles;
633
634 }
635
636 return $domObj;
637
638 }
639
640
641 }
642
643 class compileException extends Exception {
644 protected $code = -1;
645
646 }
647