PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 3.6.1
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v3.6.1
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 trunk 1.0.0 2.0.0 2.0.1 2.0.2 2.0.3 3.0 3.0.1 3.0.2 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.2.0 3.2.1 3.2.2 3.2.4 3.2.5 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1
superb-blocks / src / data / csstidy / class.csstidy_optimise.php
superb-blocks / src / data / csstidy Last commit date
class.csstidy.php 11 months ago class.csstidy_optimise.php 11 months ago class.csstidy_print.php 11 months ago data.inc.php 11 months ago
class.csstidy_optimise.php
1407 lines
1 <?php
2
3 namespace SuperbAddons\CSSTidy;
4 // Added namespace and moved defines to class consts
5
6 /**
7 * CSSTidy - CSS Parser and Optimiser
8 *
9 * CSS Optimising Class
10 * This class optimises CSS data generated by csstidy.
11 *
12 * Copyright 2005, 2006, 2007 Florian Schmitz
13 *
14 * This file is part of CSSTidy.
15 *
16 * CSSTidy is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU Lesser General Public License as published by
18 * the Free Software Foundation; either version 2.1 of the License, or
19 * (at your option) any later version.
20 *
21 * CSSTidy is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU Lesser General Public License for more details.
25 *
26 * You should have received a copy of the GNU Lesser General Public License
27 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 *
29 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
30 * @package csstidy
31 * @author Florian Schmitz (floele at gmail dot com) 2005-2007
32 * @author Brett Zamir (brettz9 at yahoo dot com) 2007
33 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
34 * @author Cedric Morin (cedric at yterium dot com) 2010-2012
35 */
36
37 /**
38 * CSS Optimising Class
39 *
40 * This class optimises CSS data generated by csstidy.
41 *
42 * @package csstidy
43 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
44 * @version 1.0
45 */
46 class csstidy_optimise
47 {
48
49 /**
50 * csstidy object
51 * @var object
52 */
53 public $parser;
54 public $css;
55 public $sub_value;
56 public $at;
57 public $selector;
58 public $property;
59 public $value;
60
61 /**
62 * Constructor
63 * @param array $css contains the class csstidy
64 * @access private
65 * @version 1.0
66 */
67 public function __construct($css)
68 {
69 $this->parser = $css;
70 $this->css = &$css->css;
71 $this->sub_value = &$css->sub_value;
72 $this->at = &$css->at;
73 $this->selector = &$css->selector;
74 $this->property = &$css->property;
75 $this->value = &$css->value;
76 }
77
78 /**
79 * Optimises $css after parsing
80 * @access public
81 * @version 1.0
82 */
83 public function postparse()
84 {
85
86 if ($this->parser->get_cfg('reverse_left_and_right') > 0) {
87
88 foreach ($this->css as $medium => $selectors) {
89 if (is_array($selectors)) {
90 foreach ($selectors as $selector => $properties) {
91 $this->css[$medium][$selector] = $this->reverse_left_and_right($this->css[$medium][$selector]);
92 }
93 }
94 }
95 }
96
97 if ($this->parser->get_cfg('preserve_css')) {
98 return;
99 }
100
101 if ((int)$this->parser->get_cfg('merge_selectors') === 2) {
102 foreach ($this->css as $medium => $value) {
103 if (is_array($value)) {
104 $this->merge_selectors($this->css[$medium]);
105 }
106 }
107 }
108
109 if ($this->parser->get_cfg('discard_invalid_selectors')) {
110 foreach ($this->css as $medium => $value) {
111 if (is_array($value)) {
112 $this->discard_invalid_selectors($this->css[$medium]);
113 }
114 }
115 }
116
117 if ($this->parser->get_cfg('optimise_shorthands') > 0) {
118 foreach ($this->css as $medium => $value) {
119 if (is_array($value)) {
120 foreach ($value as $selector => $value1) {
121 $this->css[$medium][$selector] = $this->merge_4value_shorthands($this->css[$medium][$selector]);
122 $this->css[$medium][$selector] = $this->merge_4value_radius_shorthands($this->css[$medium][$selector]);
123
124 if ($this->parser->get_cfg('optimise_shorthands') < 2) {
125 continue;
126 }
127
128 $this->css[$medium][$selector] = $this->merge_font($this->css[$medium][$selector]);
129
130 if ($this->parser->get_cfg('optimise_shorthands') < 3) {
131 continue;
132 }
133
134 $this->css[$medium][$selector] = $this->merge_bg($this->css[$medium][$selector]);
135 if (empty($this->css[$medium][$selector])) {
136 unset($this->css[$medium][$selector]);
137 }
138 }
139 }
140 }
141 }
142 }
143
144 /**
145 * Optimises values
146 * @access public
147 * @version 1.0
148 */
149 public function value()
150 {
151 $shorthands = &$this->parser->data['csstidy']['shorthands'];
152
153 // optimise shorthand properties
154 if (isset($shorthands[$this->property])) {
155 $temp = $this->shorthand($this->value); // FIXME - move
156 if ($temp != $this->value) {
157 $this->parser->log('Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information');
158 }
159 $this->value = $temp;
160 }
161
162 // Remove whitespace at ! important
163 if ($this->value != $this->compress_important($this->value)) {
164 $this->parser->log('Optimised !important', 'Information');
165 }
166 }
167
168 /**
169 * Optimises shorthands
170 * @access public
171 * @version 1.0
172 */
173 public function shorthands()
174 {
175 $shorthands = &$this->parser->data['csstidy']['shorthands'];
176
177 if (!$this->parser->get_cfg('optimise_shorthands') || $this->parser->get_cfg('preserve_css')) {
178 return;
179 }
180
181 if ($this->property === 'font' && $this->parser->get_cfg('optimise_shorthands') > 1) {
182 $this->css[$this->at][$this->selector]['font'] = '';
183 $this->parser->merge_css_blocks($this->at, $this->selector, $this->dissolve_short_font($this->value));
184 }
185 if ($this->property === 'background' && $this->parser->get_cfg('optimise_shorthands') > 2) {
186 $this->css[$this->at][$this->selector]['background'] = '';
187 $this->parser->merge_css_blocks($this->at, $this->selector, $this->dissolve_short_bg($this->value));
188 }
189 if (isset($shorthands[$this->property])) {
190 $this->parser->merge_css_blocks($this->at, $this->selector, $this->dissolve_4value_shorthands($this->property, $this->value));
191 if (is_array($shorthands[$this->property])) {
192 $this->css[$this->at][$this->selector][$this->property] = '';
193 }
194 }
195 }
196
197 /**
198 * Optimises a sub-value
199 * @access public
200 * @version 1.0
201 */
202 public function subvalue()
203 {
204 $replace_colors = &$this->parser->data['csstidy']['replace_colors'];
205
206 $this->sub_value = trim($this->sub_value);
207 if ($this->sub_value == '') { // caution : '0'
208 return;
209 }
210
211 $important = '';
212 if ($this->parser->is_important($this->sub_value)) {
213 $important = '!important';
214 }
215 $this->sub_value = $this->parser->gvw_important($this->sub_value);
216
217 // Compress font-weight
218 if ($this->property === 'font-weight' && $this->parser->get_cfg('compress_font-weight')) {
219 if ($this->sub_value === 'bold') {
220 $this->sub_value = '700';
221 $this->parser->log('Optimised font-weight: Changed "bold" to "700"', 'Information');
222 } elseif ($this->sub_value === 'normal') {
223 $this->sub_value = '400';
224 $this->parser->log('Optimised font-weight: Changed "normal" to "400"', 'Information');
225 }
226 }
227
228 $temp = $this->compress_numbers($this->sub_value);
229 if (strcasecmp($temp, $this->sub_value) !== 0) {
230 if (strlen($temp) > strlen($this->sub_value)) {
231 $this->parser->log('Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning');
232 } else {
233 $this->parser->log('Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information');
234 }
235 $this->sub_value = $temp;
236 }
237 if ($this->parser->get_cfg('compress_colors')) {
238 $temp = $this->cut_color($this->sub_value);
239 if ($temp !== $this->sub_value) {
240 if (isset($replace_colors[$this->sub_value])) {
241 $this->parser->log('Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning');
242 } else {
243 $this->parser->log('Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information');
244 }
245 $this->sub_value = $temp;
246 }
247 }
248 $this->sub_value .= $important;
249 }
250
251 /**
252 * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px
253 * @param string $value
254 * @access public
255 * @return string
256 * @version 1.0
257 */
258 public function shorthand($value)
259 {
260 $important = '';
261 if ($this->parser->is_important($value)) {
262 $values = $this->parser->gvw_important($value);
263 $important = '!important';
264 } else
265 $values = $value;
266
267 $values = explode(' ', $values);
268 switch (count($values)) {
269 case 4:
270 if ($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3]) {
271 return $values[0] . $important;
272 } elseif ($values[1] == $values[3] && $values[0] == $values[2]) {
273 return $values[0] . ' ' . $values[1] . $important;
274 } elseif ($values[1] == $values[3]) {
275 return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important;
276 }
277 break;
278
279 case 3:
280 if ($values[0] == $values[1] && $values[0] == $values[2]) {
281 return $values[0] . $important;
282 } elseif ($values[0] == $values[2]) {
283 return $values[0] . ' ' . $values[1] . $important;
284 }
285 break;
286
287 case 2:
288 if ($values[0] == $values[1]) {
289 return $values[0] . $important;
290 }
291 break;
292 }
293
294 return $value;
295 }
296
297 /**
298 * Removes unnecessary whitespace in ! important
299 * @param string $string
300 * @return string
301 * @access public
302 * @version 1.1
303 */
304 public function compress_important(&$string)
305 {
306 if ($this->parser->is_important($string)) {
307 $important = $this->parser->get_cfg('space_before_important') ? ' !important' : '!important';
308 $string = $this->parser->gvw_important($string) . $important;
309 }
310 return $string;
311 }
312
313 /**
314 * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
315 * @param string $color
316 * @return string
317 * @version 1.1
318 */
319 public function cut_color($color)
320 {
321 $replace_colors = &$this->parser->data['csstidy']['replace_colors'];
322
323 // if it's a string, don't touch !
324 if (strncmp($color, "'", 1) == 0 || strncmp($color, '"', 1) == 0)
325 return $color;
326
327 /* expressions complexes de type gradient */
328 if (
329 strpos($color, '(') !== false
330 && (strncasecmp($color, 'rgb(', 4) !== 0 and strncasecmp($color, 'rgba(', 5) !== 0)
331 ) {
332 // on ne touche pas aux couleurs dans les expression ms, c'est trop sensible
333 if (stripos($color, 'progid:') !== false)
334 return $color;
335 preg_match_all(",rgba?\([^)]+\),i", $color, $matches, PREG_SET_ORDER);
336 if (count($matches)) {
337 foreach ($matches as $m) {
338 $color = str_replace($m[0], $this->cut_color($m[0]), $color);
339 }
340 }
341 preg_match_all(",#[0-9a-f]{6}(?=[^0-9a-f]),i", $color, $matches, PREG_SET_ORDER);
342 if (count($matches)) {
343 foreach ($matches as $m) {
344 $color = str_replace($m[0], $this->cut_color($m[0]), $color);
345 }
346 }
347 return $color;
348 }
349
350 // rgb(0,0,0) -> #000000 (or #000 in this case later)
351 if (
352 // be sure to not corrupt a rgb with calc() value
353 (strncasecmp($color, 'rgb(', 4) == 0 and strpos($color, '(', 4) === false)
354 or (strncasecmp($color, 'rgba(', 5) == 0 and strpos($color, '(', 5) === false)
355 ) {
356 $color_tmp = explode('(', $color, 2);
357 $color_tmp = rtrim(end($color_tmp), ')');
358 if (strpos($color_tmp, '/') !== false) {
359 $color_tmp = explode('/', $color_tmp, 2);
360 $color_parts = explode(' ', trim(reset($color_tmp)), 3);
361 while (count($color_parts) < 3) {
362 $color_parts[] = 0;
363 }
364 $color_parts[] = end($color_tmp);
365 } else {
366 $color_parts = explode(',', $color_tmp, 4);
367 }
368 for ($i = 0; $i < count($color_parts); $i++) {
369 $color_parts[$i] = trim($color_parts[$i]);
370 if (substr($color_parts[$i], -1) === '%') {
371 $color_parts[$i] = round((255 * intval($color_parts[$i])) / 100);
372 } elseif ($i > 2) {
373 // 4th argument is alpga layer between 0 and 1 (if not %)
374 $color_parts[$i] = round((255 * floatval($color_parts[$i])));
375 }
376 $color_parts[$i] = intval($color_parts[$i]);
377 if ($color_parts[$i] > 255) {
378 $color_parts[$i] = 255;
379 }
380 }
381 $color = '#';
382 // 3 or 4 parts depending on alpha layer
383 $nb = min(max(count($color_parts), 3), 4);
384 for ($i = 0; $i < $nb; $i++) {
385 if (!isset($color_parts[$i])) {
386 $color_parts[$i] = 0;
387 }
388 if ($color_parts[$i] < 16) {
389 $color .= '0' . dechex($color_parts[$i]);
390 } else {
391 $color .= dechex($color_parts[$i]);
392 }
393 }
394 }
395
396 // Fix bad color names
397 if (isset($replace_colors[strtolower($color)])) {
398 $color = $replace_colors[strtolower($color)];
399 }
400
401 // #aabbcc -> #abc
402 if (strlen($color) == 7) {
403 $color_temp = strtolower($color);
404 if ($color_temp[0] === '#' && $color_temp[1] == $color_temp[2] && $color_temp[3] == $color_temp[4] && $color_temp[5] == $color_temp[6]) {
405 $color = '#' . $color[1] . $color[3] . $color[5];
406 }
407 }
408 // #aabbccdd -> #abcd
409 elseif (strlen($color) == 9) {
410 $color_temp = strtolower($color);
411 if ($color_temp[0] === '#' && $color_temp[1] == $color_temp[2] && $color_temp[3] == $color_temp[4] && $color_temp[5] == $color_temp[6] && $color_temp[7] == $color_temp[8]) {
412 $color = '#' . $color[1] . $color[3] . $color[5] . $color[7];
413 }
414 }
415
416 switch (strtolower($color)) {
417 /* color name -> hex code */
418 case 'black':
419 return '#000';
420 case 'fuchsia':
421 return '#f0f';
422 case 'white':
423 return '#fff';
424 case 'yellow':
425 return '#ff0';
426
427 /* hex code -> color name */
428 case '#800000':
429 return 'maroon';
430 case '#ffa500':
431 return 'orange';
432 case '#808000':
433 return 'olive';
434 case '#800080':
435 return 'purple';
436 case '#008000':
437 return 'green';
438 case '#000080':
439 return 'navy';
440 case '#008080':
441 return 'teal';
442 case '#c0c0c0':
443 return 'silver';
444 case '#808080':
445 return 'gray';
446 case '#f00':
447 return 'red';
448 }
449
450 return $color;
451 }
452
453 /**
454 * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
455 * @param string $subvalue
456 * @return string
457 * @version 1.2
458 */
459 public function compress_numbers($subvalue)
460 {
461 $unit_values = &$this->parser->data['csstidy']['unit_values'];
462 $color_values = &$this->parser->data['csstidy']['color_values'];
463
464 // for font:1em/1em sans-serif...;
465 if ($this->property === 'font') {
466 $temp = explode('/', $subvalue);
467 } else {
468 $temp = array($subvalue);
469 }
470
471 for ($l = 0; $l < count($temp); $l++) {
472 // if we are not dealing with a number at this point, do not optimise anything
473 $number = $this->AnalyseCssNumber($temp[$l]);
474 if ($number === false) {
475 return $subvalue;
476 }
477
478 // Fix bad colors
479 if (in_array($this->property, $color_values)) {
480 $temp[$l] = '#' . $temp[$l];
481 continue;
482 }
483
484 if (abs($number[0]) > 0) {
485 if ($number[1] == '' && in_array($this->property, $unit_values, true)) {
486 $number[1] = 'px';
487 }
488 } elseif ($number[1] != 's' && $number[1] != 'ms') {
489 $number[1] = '';
490 }
491
492 $temp[$l] = $number[0] . $number[1];
493 }
494
495 return ((count($temp) > 1) ? $temp[0] . '/' . $temp[1] : $temp[0]);
496 }
497
498 /**
499 * Checks if a given string is a CSS valid number. If it is,
500 * an array containing the value and unit is returned
501 * @param string $string
502 * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number
503 */
504 public function AnalyseCssNumber($string)
505 {
506 // most simple checks first
507 if (strlen($string) == 0 || ctype_alpha($string[0])) {
508 return false;
509 }
510
511 $units = &$this->parser->data['csstidy']['units'];
512 $return = array(0, '');
513
514 $return[0] = floatval($string);
515 if (abs($return[0]) > 0 && abs($return[0]) < 1) {
516 if ($return[0] < 0) {
517 $return[0] = '-' . ltrim(substr($return[0], 1), '0');
518 } else {
519 $return[0] = ltrim($return[0], '0');
520 }
521 }
522
523 // Look for unit and split from value if exists
524 foreach ($units as $unit) {
525 $expectUnitAt = strlen($string) - strlen($unit);
526 if (!($unitInString = stristr($string, $unit))) { // mb_strpos() fails with "false"
527 continue;
528 }
529 $actualPosition = strpos($string, $unitInString);
530 if ($expectUnitAt === $actualPosition) {
531 $return[1] = $unit;
532 $string = substr($string, 0, -strlen($unit));
533 break;
534 }
535 }
536 if (!is_numeric($string)) {
537 return false;
538 }
539 return $return;
540 }
541
542 /**
543 * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red}
544 * Very basic and has at least one bug. Hopefully there is a replacement soon.
545 * @param array $array
546 * @return array
547 * @access public
548 * @version 1.2
549 */
550 public function merge_selectors(&$array)
551 {
552 $css = $array;
553 foreach ($css as $key => $value) {
554 if (!isset($css[$key])) {
555 continue;
556 }
557 $newsel = '';
558
559 // Check if properties also exist in another selector
560 $keys = array();
561 // PHP bug (?) without $css = $array; here
562 foreach ($css as $selector => $vali) {
563 if ($selector == $key) {
564 continue;
565 }
566
567 if ($css[$key] === $vali) {
568 $keys[] = $selector;
569 }
570 }
571
572 if (!empty($keys)) {
573 $newsel = $key;
574 unset($css[$key]);
575 foreach ($keys as $selector) {
576 unset($css[$selector]);
577 $newsel .= ',' . $selector;
578 }
579 $css[$newsel] = $value;
580 }
581 }
582 $array = $css;
583 }
584
585 /**
586 * Removes invalid selectors and their corresponding rule-sets as
587 * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check
588 * and should be replaced by a full-blown parsing algorithm or
589 * regular expression
590 * @version 1.4
591 */
592 public function discard_invalid_selectors(&$array)
593 {
594 $invalid = array('+' => true, '~' => true, ',' => true, '>' => true);
595 foreach ($array as $selector => $decls) {
596 $ok = true;
597 $selectors = array_map('trim', explode(',', $selector));
598 foreach ($selectors as $s) {
599 $simple_selectors = preg_split('/\s*[+>~\s]\s*/', $s);
600 foreach ($simple_selectors as $ss) {
601 if ($ss === '')
602 $ok = false;
603 // could also check $ss for internal structure,
604 // but that probably would be too slow
605 }
606 }
607 if (!$ok)
608 unset($array[$selector]);
609 }
610 }
611
612 /**
613 * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;...
614 * @param string $property
615 * @param string $value
616 * @param array|null $shorthands
617 * @return array
618 * @version 1.0
619 * @see merge_4value_shorthands()
620 */
621 public function dissolve_4value_shorthands($property, $value, $shorthands = null)
622 {
623 if (is_null($shorthands)) {
624 $shorthands = &$this->parser->data['csstidy']['shorthands'];
625 }
626 if (!is_array($shorthands[$property])) {
627 $return[$property] = $value;
628 return $return;
629 }
630
631 $important = '';
632 if ($this->parser->is_important($value)) {
633 $value = $this->parser->gvw_important($value);
634 $important = '!important';
635 }
636 $values = explode(' ', $value);
637
638
639 $return = array();
640 if (count($values) == 4) {
641 for ($i = 0; $i < 4; $i++) {
642 $return[$shorthands[$property][$i]] = $values[$i] . $important;
643 }
644 } elseif (count($values) == 3) {
645 $return[$shorthands[$property][0]] = $values[0] . $important;
646 $return[$shorthands[$property][1]] = $values[1] . $important;
647 $return[$shorthands[$property][3]] = $values[1] . $important;
648 $return[$shorthands[$property][2]] = $values[2] . $important;
649 } elseif (count($values) == 2) {
650 for ($i = 0; $i < 4; $i++) {
651 $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1] . $important : $values[0] . $important;
652 }
653 } else {
654 for ($i = 0; $i < 4; $i++) {
655 $return[$shorthands[$property][$i]] = $values[0] . $important;
656 }
657 }
658
659 return $return;
660 }
661
662 /**
663 * Dissolves radius properties like
664 * border-radius:10px 10px 10px / 1px 2px
665 * to border-top-left:10px 1px;border-top-right:10px 2x;...
666 * @param string $property
667 * @param string $value
668 * @return array
669 * @version 1.0
670 * @use dissolve_4value_shorthands()
671 * @see merge_4value_radius_shorthands()
672 */
673 public function dissolve_4value_radius_shorthands($property, $value)
674 {
675 $shorthands = &$this->parser->data['csstidy']['radius_shorthands'];
676 if (!is_array($shorthands[$property])) {
677 $return[$property] = $value;
678 return $return;
679 }
680
681 if (strpos($value, '/') !== false) {
682 $values = $this->explode_ws('/', $value);
683 if (count($values) == 2) {
684 $r[0] = $this->dissolve_4value_shorthands($property, trim($values[0]), $shorthands);
685 $r[1] = $this->dissolve_4value_shorthands($property, trim($values[1]), $shorthands);
686 $return = array();
687 foreach ($r[0] as $p => $v) {
688 $return[$p] = $v;
689 if ($r[1][$p] !== $v) {
690 $return[$p] .= ' ' . $r[1][$p];
691 }
692 }
693 return $return;
694 }
695 }
696
697 $return = $this->dissolve_4value_shorthands($property, $value, $shorthands);
698 return $return;
699 }
700
701 /**
702 * Explodes a string as explode() does, however, not if $sep is escaped or within a string.
703 * @param string $sep seperator
704 * @param string $string
705 * @param bool $explode_in_parenthesis
706 * @return array
707 * @version 1.0
708 */
709 public function explode_ws($sep, $string, $explode_in_parenthesis = false)
710 {
711 $status = 'st';
712 $to = '';
713
714 $output = array(
715 0 => '',
716 );
717 $num = 0;
718 for ($i = 0, $len = strlen($string); $i < $len; $i++) {
719 switch ($status) {
720 case 'st':
721 if ($string[$i] == $sep && !$this->parser->escaped($string, $i)) {
722 ++$num;
723 } elseif ($string[$i] === '"' || $string[$i] === '\'' || (!$explode_in_parenthesis && $string[$i] === '(') && !$this->parser->escaped($string, $i)) {
724 $status = 'str';
725 $to = ($string[$i] === '(') ? ')' : $string[$i];
726 (isset($output[$num])) ? $output[$num] .= $string[$i] : $output[$num] = $string[$i];
727 } else {
728 (isset($output[$num])) ? $output[$num] .= $string[$i] : $output[$num] = $string[$i];
729 }
730 break;
731
732 case 'str':
733 if ($string[$i] == $to && !$this->parser->escaped($string, $i)) {
734 $status = 'st';
735 }
736 (isset($output[$num])) ? $output[$num] .= $string[$i] : $output[$num] = $string[$i];
737 break;
738 }
739 }
740
741 return $output;
742 }
743
744 /**
745 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
746 * @param array $array
747 * @param array|null $shorthands
748 * @return array
749 * @version 1.2
750 * @see dissolve_4value_shorthands()
751 */
752 public function merge_4value_shorthands($array, $shorthands = null)
753 {
754 $return = $array;
755 if (is_null($shorthands)) {
756 $shorthands = &$this->parser->data['csstidy']['shorthands'];
757 }
758
759 foreach ($shorthands as $key => $value) {
760 if (
761 $value !== 0 && isset($array[$value[0]]) && isset($array[$value[1]])
762 && isset($array[$value[2]]) && isset($array[$value[3]])
763 ) {
764 $return[$key] = '';
765
766 $important = '';
767 for ($i = 0; $i < 4; $i++) {
768 $val = $array[$value[$i]];
769 if ($this->parser->is_important($val)) {
770 $important = '!important';
771 $return[$key] .= $this->parser->gvw_important($val) . ' ';
772 } else {
773 $return[$key] .= $val . ' ';
774 }
775 unset($return[$value[$i]]);
776 }
777 $return[$key] = $this->shorthand(trim($return[$key] . $important));
778 }
779 }
780 return $return;
781 }
782
783 /**
784 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
785 * @param array $array
786 * @return array
787 * @version 1.2
788 * @use merge_4value_shorthands()
789 * @see dissolve_4value_radius_shorthands()
790 */
791 public function merge_4value_radius_shorthands($array)
792 {
793 $return = $array;
794 $shorthands = &$this->parser->data['csstidy']['radius_shorthands'];
795
796 foreach ($shorthands as $key => $value) {
797 if (
798 isset($array[$value[0]]) && isset($array[$value[1]])
799 && isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0
800 ) {
801 $return[$key] = '';
802 $a = array();
803 for ($i = 0; $i < 4; $i++) {
804 $v = $this->explode_ws(' ', trim($array[$value[$i]]));
805 $a[0][$value[$i]] = reset($v);
806 $a[1][$value[$i]] = end($v);
807 }
808 $r = array();
809 $r[0] = $this->merge_4value_shorthands($a[0], $shorthands);
810 $r[1] = $this->merge_4value_shorthands($a[1], $shorthands);
811
812 if (isset($r[0][$key]) and isset($r[1][$key])) {
813 $return[$key] = $r[0][$key];
814 if ($r[1][$key] !== $r[0][$key]) {
815 $return[$key] .= ' / ' . $r[1][$key];
816 }
817 for ($i = 0; $i < 4; $i++) {
818 unset($return[$value[$i]]);
819 }
820 }
821 }
822 }
823 return $return;
824 }
825 /**
826 * Dissolve background property
827 * @param string $str_value
828 * @return array
829 * @version 1.0
830 * @see merge_bg()
831 * @todo full CSS 3 compliance
832 */
833 public function dissolve_short_bg($str_value)
834 {
835 // don't try to explose background gradient !
836 if (stripos($str_value, 'gradient(') !== false)
837 return array('background' => $str_value);
838
839 $background_prop_default = &$this->parser->data['csstidy']['background_prop_default'];
840 $repeat = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space');
841 $attachment = array('scroll', 'fixed', 'local');
842 $clip = array('border', 'padding');
843 $origin = array('border', 'padding', 'content');
844 $pos = array('top', 'center', 'bottom', 'left', 'right');
845 $important = '';
846 $return = array('background-image' => null, 'background-size' => null, 'background-repeat' => null, 'background-position' => null, 'background-attachment' => null, 'background-clip' => null, 'background-origin' => null, 'background-color' => null);
847
848 if ($this->parser->is_important($str_value)) {
849 $important = ' !important';
850 $str_value = $this->parser->gvw_important($str_value);
851 }
852
853 $str_value = $this->explode_ws(',', $str_value);
854 for ($i = 0; $i < count($str_value); $i++) {
855 $have['clip'] = false;
856 $have['pos'] = false;
857 $have['color'] = false;
858 $have['bg'] = false;
859
860 if (is_array($str_value[$i])) {
861 $str_value[$i] = $str_value[$i][0];
862 }
863 $str_value[$i] = $this->explode_ws(' ', trim($str_value[$i]));
864
865 for ($j = 0; $j < count($str_value[$i]); $j++) {
866 if ($have['bg'] === false && (substr($str_value[$i][$j], 0, 4) === 'url(' || $str_value[$i][$j] === 'none')) {
867 $return['background-image'] .= $str_value[$i][$j] . ',';
868 $have['bg'] = true;
869 } elseif (in_array($str_value[$i][$j], $repeat, true)) {
870 $return['background-repeat'] .= $str_value[$i][$j] . ',';
871 } elseif (in_array($str_value[$i][$j], $attachment, true)) {
872 $return['background-attachment'] .= $str_value[$i][$j] . ',';
873 } elseif (in_array($str_value[$i][$j], $clip, true) && !$have['clip']) {
874 $return['background-clip'] .= $str_value[$i][$j] . ',';
875 $have['clip'] = true;
876 } elseif (in_array($str_value[$i][$j], $origin, true)) {
877 $return['background-origin'] .= $str_value[$i][$j] . ',';
878 } elseif ($str_value[$i][$j][0] === '(') {
879 $return['background-size'] .= substr($str_value[$i][$j], 1, -1) . ',';
880 } elseif (in_array($str_value[$i][$j], $pos, true) || is_numeric($str_value[$i][$j][0]) || $str_value[$i][$j][0] === null || $str_value[$i][$j][0] === '-' || $str_value[$i][$j][0] === '.') {
881 $return['background-position'] .= $str_value[$i][$j];
882 if (!$have['pos'])
883 $return['background-position'] .= ' ';
884 else
885 $return['background-position'] .= ',';
886 $have['pos'] = true;
887 } elseif (!$have['color']) {
888 $return['background-color'] .= $str_value[$i][$j] . ',';
889 $have['color'] = true;
890 }
891 }
892 }
893
894 foreach ($background_prop_default as $bg_prop => $default_value) {
895 if ($return[$bg_prop] !== null) {
896 $return[$bg_prop] = substr($return[$bg_prop], 0, -1) . $important;
897 } else
898 $return[$bg_prop] = $default_value . $important;
899 }
900 return $return;
901 }
902
903 /**
904 * Merges all background properties
905 * @param array $input_css
906 * @return array
907 * @version 1.0
908 * @see dissolve_short_bg()
909 * @todo full CSS 3 compliance
910 */
911 public function merge_bg($input_css)
912 {
913 $background_prop_default = &$this->parser->data['csstidy']['background_prop_default'];
914 // Max number of background images. CSS3 not yet fully implemented
915 $number_of_values = @max(count($this->explode_ws(',', $input_css['background-image'])), count($this->explode_ws(',', $input_css['background-color'])), 1);
916 // Array with background images to check if BG image exists
917 $bg_img_array = @$this->explode_ws(',', $this->parser->gvw_important($input_css['background-image']));
918 $new_bg_value = '';
919 $important = '';
920
921 // if background properties is here and not empty, don't try anything
922 if (isset($input_css['background']) && $input_css['background'])
923 return $input_css;
924
925 for ($i = 0; $i < $number_of_values; $i++) {
926 foreach ($background_prop_default as $bg_property => $default_value) {
927 // Skip if property does not exist
928 if (!isset($input_css[$bg_property])) {
929 continue;
930 }
931
932 $cur_value = $input_css[$bg_property];
933 // skip all optimisation if gradient() somewhere
934 if (stripos($cur_value, 'gradient(') !== false)
935 return $input_css;
936
937 // Skip some properties if there is no background image
938 if ((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none')
939 && ($bg_property === 'background-size' || $bg_property === 'background-position'
940 || $bg_property === 'background-attachment' || $bg_property === 'background-repeat')
941 ) {
942 continue;
943 }
944
945 // Remove !important
946 if ($this->parser->is_important($cur_value)) {
947 $important = ' !important';
948 $cur_value = $this->parser->gvw_important($cur_value);
949 }
950
951 // Do not add default values
952 if ($cur_value === $default_value) {
953 continue;
954 }
955
956 $temp = $this->explode_ws(',', $cur_value);
957
958 if (isset($temp[$i])) {
959 if ($bg_property === 'background-size') {
960 $new_bg_value .= '(' . $temp[$i] . ') ';
961 } else {
962 $new_bg_value .= $temp[$i] . ' ';
963 }
964 }
965 }
966
967 $new_bg_value = trim($new_bg_value);
968 if ($i != $number_of_values - 1)
969 $new_bg_value .= ',';
970 }
971
972 // Delete all background-properties
973 foreach ($background_prop_default as $bg_property => $default_value) {
974 unset($input_css[$bg_property]);
975 }
976
977 // Add new background property
978 if ($new_bg_value !== '')
979 $input_css['background'] = $new_bg_value . $important;
980 elseif (isset($input_css['background']))
981 $input_css['background'] = 'none';
982
983 return $input_css;
984 }
985
986 /**
987 * Dissolve font property
988 * @param string $str_value
989 * @return array
990 * @version 1.3
991 * @see merge_font()
992 */
993 public function dissolve_short_font($str_value)
994 {
995 $font_prop_default = &$this->parser->data['csstidy']['font_prop_default'];
996 $font_weight = array('normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900);
997 $font_variant = array('normal', 'small-caps');
998 $font_style = array('normal', 'italic', 'oblique');
999 $important = '';
1000 $return = array('font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null);
1001
1002 if ($this->parser->is_important($str_value)) {
1003 $important = '!important';
1004 $str_value = $this->parser->gvw_important($str_value);
1005 }
1006
1007 $have['style'] = false;
1008 $have['variant'] = false;
1009 $have['weight'] = false;
1010 $have['size'] = false;
1011 // Detects if font-family consists of several words w/o quotes
1012 $multiwords = false;
1013
1014 // Workaround with multiple font-family
1015 $str_value = $this->explode_ws(',', trim($str_value));
1016
1017 $str_value[0] = $this->explode_ws(' ', trim($str_value[0]));
1018
1019 for ($j = 0; $j < count($str_value[0]); $j++) {
1020 if ($have['weight'] === false && in_array($str_value[0][$j], $font_weight)) {
1021 $return['font-weight'] = $str_value[0][$j];
1022 $have['weight'] = true;
1023 } elseif ($have['variant'] === false && in_array($str_value[0][$j], $font_variant)) {
1024 $return['font-variant'] = $str_value[0][$j];
1025 $have['variant'] = true;
1026 } elseif ($have['style'] === false && in_array($str_value[0][$j], $font_style)) {
1027 $return['font-style'] = $str_value[0][$j];
1028 $have['style'] = true;
1029 } elseif ($have['size'] === false && (is_numeric($str_value[0][$j][0]) || $str_value[0][$j][0] === null || $str_value[0][$j][0] === '.')) {
1030 $size = $this->explode_ws('/', trim($str_value[0][$j]));
1031 $return['font-size'] = $size[0];
1032 if (isset($size[1])) {
1033 $return['line-height'] = $size[1];
1034 } else {
1035 $return['line-height'] = ''; // don't add 'normal' !
1036 }
1037 $have['size'] = true;
1038 } else {
1039 if (isset($return['font-family'])) {
1040 $return['font-family'] .= ' ' . $str_value[0][$j];
1041 $multiwords = true;
1042 } else {
1043 $return['font-family'] = $str_value[0][$j];
1044 }
1045 }
1046 }
1047 // add quotes if we have several qords in font-family
1048 if ($multiwords !== false) {
1049 $return['font-family'] = '"' . $return['font-family'] . '"';
1050 }
1051 $i = 1;
1052 while (isset($str_value[$i])) {
1053 $return['font-family'] .= ',' . trim($str_value[$i]);
1054 $i++;
1055 }
1056
1057 // Fix for 100 and more font-size
1058 if (
1059 $have['size'] === false && isset($return['font-weight']) &&
1060 is_numeric($return['font-weight'][0])
1061 ) {
1062 $return['font-size'] = $return['font-weight'];
1063 unset($return['font-weight']);
1064 }
1065
1066 foreach ($font_prop_default as $font_prop => $default_value) {
1067 if ($return[$font_prop] !== null) {
1068 $return[$font_prop] = $return[$font_prop] . $important;
1069 } else
1070 $return[$font_prop] = $default_value . $important;
1071 }
1072 return $return;
1073 }
1074
1075 /**
1076 * Merges all fonts properties
1077 * @param array $input_css
1078 * @return array
1079 * @version 1.3
1080 * @see dissolve_short_font()
1081 */
1082 public function merge_font($input_css)
1083 {
1084 $font_prop_default = &$this->parser->data['csstidy']['font_prop_default'];
1085 $new_font_value = '';
1086 $important = '';
1087 // Skip if not font-family and font-size set
1088 if (isset($input_css['font-family']) && isset($input_css['font-size']) && $input_css['font-family'] != 'inherit') {
1089 // fix several words in font-family - add quotes
1090 if (isset($input_css['font-family'])) {
1091 $families = explode(',', $input_css['font-family']);
1092 $result_families = array();
1093 foreach ($families as $family) {
1094 $family = trim($family);
1095 $len = strlen($family);
1096 if (
1097 strpos($family, ' ') &&
1098 !(($family[0] === '"' && $family[$len - 1] === '"') ||
1099 ($family[0] === "'" && $family[$len - 1] === "'"))
1100 ) {
1101 $family = '"' . $family . '"';
1102 }
1103 $result_families[] = $family;
1104 }
1105 $input_css['font-family'] = implode(',', $result_families);
1106 }
1107 foreach ($font_prop_default as $font_property => $default_value) {
1108
1109 // Skip if property does not exist
1110 if (!isset($input_css[$font_property])) {
1111 continue;
1112 }
1113
1114 $cur_value = $input_css[$font_property];
1115
1116 // Skip if default value is used
1117 if ($cur_value === $default_value) {
1118 continue;
1119 }
1120
1121 // Remove !important
1122 if ($this->parser->is_important($cur_value)) {
1123 $important = '!important';
1124 $cur_value = $this->parser->gvw_important($cur_value);
1125 }
1126
1127 $new_font_value .= $cur_value;
1128 // Add delimiter
1129 $new_font_value .= ($font_property === 'font-size' &&
1130 isset($input_css['line-height'])) ? '/' : ' ';
1131 }
1132
1133 $new_font_value = trim($new_font_value);
1134
1135 // Delete all font-properties
1136 foreach ($font_prop_default as $font_property => $default_value) {
1137 if ($font_property !== 'font' || !$new_font_value)
1138 unset($input_css[$font_property]);
1139 }
1140
1141 // Add new font property
1142 if ($new_font_value !== '') {
1143 $input_css['font'] = $new_font_value . $important;
1144 }
1145 }
1146
1147 return $input_css;
1148 }
1149
1150 /**
1151 * Reverse left vs right in a list of properties/values
1152 * @param array $array
1153 * @return array
1154 */
1155 public function reverse_left_and_right($array)
1156 {
1157 $return = array();
1158
1159 // change left <-> right in properties name and values
1160 foreach ($array as $propertie => $value) {
1161
1162 if (method_exists($this, $m = 'reverse_left_and_right_' . str_replace('-', '_', trim($propertie)))) {
1163 $value = $this->$m($value);
1164 }
1165
1166 // simple replacement for properties
1167 $propertie = str_ireplace(array('left', 'right', "\x1"), array("\x1", 'left', 'right'), $propertie);
1168 // be careful for values, not modifying protected or quoted valued
1169 foreach (array('left' => "\x1", 'right' => 'left', "\x1" => 'right') as $v => $r) {
1170 if (strpos($value, $v) !== false) {
1171 // attraper les left et right separes du reste (pas au milieu d'un mot)
1172 if (in_array($v, array('left', 'right'))) {
1173 $value = preg_replace(",\\b$v\\b,", "\x0", $value);
1174 } else {
1175 $value = str_replace($v, "\x0", $value);
1176 }
1177 $value = $this->explode_ws("\x0", $value . ' ', true);
1178 $value = rtrim(implode($r, $value));
1179 $value = str_replace("\x0", $v, $value);
1180 }
1181 }
1182 $return[$propertie] = $value;
1183 }
1184
1185 return $return;
1186 }
1187
1188 /**
1189 * Reversing 4 values shorthands properties
1190 * @param string $value
1191 * @return string
1192 */
1193 public function reverse_left_and_right_4value_shorthands($property, $value)
1194 {
1195 $shorthands = &$this->parser->data['csstidy']['shorthands'];
1196 if (isset($shorthands[$property])) {
1197 $property_right = $shorthands[$property][1];
1198 $property_left = $shorthands[$property][3];
1199 $v = $this->dissolve_4value_shorthands($property, $value);
1200 if ($v[$property_left] !== $v[$property_right]) {
1201 $r = $v[$property_right];
1202 $v[$property_right] = $v[$property_left];
1203 $v[$property_left] = $r;
1204 $v = $this->merge_4value_shorthands($v);
1205 if (isset($v[$property])) {
1206 return $v[$property];
1207 }
1208 }
1209 }
1210 return $value;
1211 }
1212
1213 /**
1214 * Reversing 4 values radius shorthands properties
1215 * @param string $value
1216 * @return string
1217 */
1218 public function reverse_left_and_right_4value_radius_shorthands($property, $value)
1219 {
1220 $shorthands = &$this->parser->data['csstidy']['radius_shorthands'];
1221 if (isset($shorthands[$property])) {
1222 $v = $this->dissolve_4value_radius_shorthands($property, $value);
1223 if (
1224 $v[$shorthands[$property][0]] !== $v[$shorthands[$property][1]]
1225 or $v[$shorthands[$property][2]] !== $v[$shorthands[$property][3]]
1226 ) {
1227 $r = array(
1228 $shorthands[$property][0] => $v[$shorthands[$property][1]],
1229 $shorthands[$property][1] => $v[$shorthands[$property][0]],
1230 $shorthands[$property][2] => $v[$shorthands[$property][3]],
1231 $shorthands[$property][3] => $v[$shorthands[$property][2]],
1232 );
1233 $v = $this->merge_4value_radius_shorthands($r);
1234 if (isset($v[$property])) {
1235 return $v[$property];
1236 }
1237 }
1238 }
1239 return $value;
1240 }
1241
1242 /**
1243 * Reversing margin shorthands
1244 * @param string $value
1245 * @return string
1246 */
1247 public function reverse_left_and_right_margin($value)
1248 {
1249 return $this->reverse_left_and_right_4value_shorthands('margin', $value);
1250 }
1251
1252 /**
1253 * Reversing padding shorthands
1254 * @param string $value
1255 * @return string
1256 */
1257 public function reverse_left_and_right_padding($value)
1258 {
1259 return $this->reverse_left_and_right_4value_shorthands('padding', $value);
1260 }
1261
1262 /**
1263 * Reversing border-color shorthands
1264 * @param string $value
1265 * @return string
1266 */
1267 public function reverse_left_and_right_border_color($value)
1268 {
1269 return $this->reverse_left_and_right_4value_shorthands('border-color', $value);
1270 }
1271
1272 /**
1273 * Reversing border-style shorthands
1274 * @param string $value
1275 * @return string
1276 */
1277 public function reverse_left_and_right_border_style($value)
1278 {
1279 return $this->reverse_left_and_right_4value_shorthands('border-style', $value);
1280 }
1281
1282 /**
1283 * Reversing border-width shorthands
1284 * @param string $value
1285 * @return string
1286 */
1287 public function reverse_left_and_right_border_width($value)
1288 {
1289 return $this->reverse_left_and_right_4value_shorthands('border-width', $value);
1290 }
1291
1292 /**
1293 * Reversing border-radius shorthands
1294 * @param string $value
1295 * @return string
1296 */
1297 public function reverse_left_and_right_border_radius($value)
1298 {
1299 return $this->reverse_left_and_right_4value_radius_shorthands('border-radius', $value);
1300 }
1301
1302 /**
1303 * Reversing border-radius shorthands
1304 * @param string $value
1305 * @return string
1306 */
1307 public function reverse_left_and_right__moz_border_radius($value)
1308 {
1309 return $this->reverse_left_and_right_4value_radius_shorthands('border-radius', $value);
1310 }
1311
1312 /**
1313 * Reversing border-radius shorthands
1314 * @param string $value
1315 * @return string
1316 */
1317 public function reverse_left_and_right__webkit_border_radius($value)
1318 {
1319 return $this->reverse_left_and_right_4value_radius_shorthands('border-radius', $value);
1320 }
1321
1322
1323 /**
1324 * Reversing background shorthands
1325 * @param string $value
1326 * @return string
1327 */
1328 public function reverse_left_and_right_background($value)
1329 {
1330 $values = $this->dissolve_short_bg($value);
1331 if (isset($values['background-position']) and $values['background-position']) {
1332 $v = $this->reverse_left_and_right_background_position($values['background-position']);
1333 if ($v !== $values['background-position']) {
1334 if ($value == $values['background-position']) {
1335 return $v;
1336 } else {
1337 $values['background-position'] = $v;
1338 $x = $this->merge_bg($values);
1339 if (isset($x['background'])) {
1340 return $x['background'];
1341 }
1342 }
1343 }
1344 }
1345 return $value;
1346 }
1347
1348 /**
1349 * Reversing background position shorthands
1350 * @param string $value
1351 * @return string
1352 */
1353 public function reverse_left_and_right_background_position_x($value)
1354 {
1355 return $this->reverse_left_and_right_background_position($value);
1356 }
1357
1358 /**
1359 * Reversing background position shorthands
1360 * @param string $value
1361 * @return string
1362 */
1363 public function reverse_left_and_right_background_position($value)
1364 {
1365 // multiple background case
1366 if (strpos($value, ',') !== false) {
1367 $values = $this->explode_ws(',', $value);
1368 if (count($values) > 1) {
1369 foreach ($values as $k => $v) {
1370 $values[$k] = $this->reverse_left_and_right_background_position($v);
1371 }
1372 return implode(',', $values);
1373 }
1374 }
1375
1376 // if no explicit left or right value
1377 if (stripos($value, 'left') === false and stripos($value, 'right') === false) {
1378 $values = $this->explode_ws(' ', trim($value));
1379 $values = array_map('trim', $values);
1380 $values = array_filter($values, function ($v) {
1381 return strlen($v);
1382 });
1383 $values = array_values($values);
1384 if (count($values) == 1) {
1385 if (in_array($value, array('center', 'top', 'bottom', 'inherit', 'initial', 'unset'))) {
1386 return $value;
1387 }
1388 return "left $value";
1389 }
1390 if ($values[1] == 'top' or $values[1] == 'bottom') {
1391 if ($values[0] === 'center') {
1392 return $value;
1393 }
1394 return 'left ' . implode(' ', $values);
1395 } else {
1396 $last = array_pop($values);
1397 if ($last === 'center') {
1398 return $value;
1399 }
1400 return implode(' ', $values) . ' left ' . $last;
1401 }
1402 }
1403
1404 return $value;
1405 }
1406 }
1407