Commit e98e478e authored by Robert Lyon's avatar Robert Lyon Committed by Gerrit Code Review
Browse files

Merge "Bug 1812779: Upgrade CSS tidy lib to 1.6.5"

parents 2bebf35c 1d0d0a92
......@@ -2,7 +2,7 @@ CSSTidy in Mahara
=================
Website: https://github.com/Cerdic/CSSTidy
Version: 1.5.7
Version: 1.6.5
This library is used by clean_css, to strip out malicious CSS
......@@ -11,4 +11,4 @@ Changes:
* Add new setting 'preserve_css_comment' and don't strip out CSS comments if it is set. This is in class.csstidy.php.
Removed files we don't use:
/Docs and /testing folders
templates, travis, composer, .gitignore, css files, css_optimiser.php
\ No newline at end of file
templates, travis, composer, .gitignore, css files, css_optimiser.php
......@@ -2,7 +2,13 @@
CSSTidy is a CSS minifier
* v1.5.7 :
* v1.6.5 :
fix warnings with PHP 7.3
* v1.6.4 :
preserve important comments (starting with !) in the minification /*! Credits/Licence */
* v1.6.3 :
border-radius shorthands optimisation, reverse_left_and_right option
* v1.5.7 :
PHP 7 compatibility, composer update, Travis CI integration
* v1.5.6 :
fixes minor bugs, mainly on CSS3 properties/units
......
......@@ -61,14 +61,15 @@ if (!function_exists('ctype_xdigit')){
* Defines constants
* @todo //TODO: make them class constants of csstidy
*/
define('AT_START', 1);
define('AT_END', 2);
define('SEL_START', 3);
define('SEL_END', 4);
define('PROPERTY', 5);
define('VALUE', 6);
define('COMMENT', 7);
define('DEFAULT_AT', 41);
define('AT_START', 1);
define('AT_END', 2);
define('SEL_START', 3);
define('SEL_END', 4);
define('PROPERTY', 5);
define('VALUE', 6);
define('COMMENT', 7);
define('IMPORTANT_COMMENT',8);
define('DEFAULT_AT', 41);
/**
* Contains a class for printing CSS code
......@@ -94,7 +95,7 @@ require('class.csstidy_optimise.php');
* An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php
* @package csstidy
* @author Florian Schmitz (floele at gmail dot com) 2005-2006
* @version 1.5.7
* @version 1.6.5
*/
class csstidy {
......@@ -104,10 +105,22 @@ class csstidy {
* @access public
*/
public $css = array();
/**
* Saves the comments.
* @var array
* @access public
*/
public $comments = array();
/**
* Saves the current comments of the selectors
* @var array
* @access public
*/
public $cur_comments = array();
/**
* Saves the parsed CSS (raw)
* @var array
* @access private
* @access public
*/
public $tokens = array();
/**
......@@ -147,7 +160,7 @@ class csstidy {
* @var string
* @access private
*/
public $version = '1.5.7';
public $version = '1.6.5';
/**
* Stores the settings
* @var array
......@@ -318,6 +331,10 @@ class csstidy {
/* is dangeroues to be used: CSS is broken sometimes */
$this->settings['merge_selectors'] = 0;
/* preserve or not browser hacks */
/* Useful to produce a rtl css from a ltr one (or the opposite) */
$this->settings['reverse_left_and_right'] = 0;
$this->settings['discard_invalid_selectors'] = false;
$this->settings['discard_invalid_properties'] = false;
$this->settings['css_level'] = 'CSS3.0';
......@@ -412,7 +429,7 @@ class csstidy {
*/
public function _add_token($type, $data, $do = false) {
if ($this->get_cfg('preserve_css') || $do) {
$this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data));
$this->tokens[] = array($type, ($type == COMMENT or $type == IMPORTANT_COMMENT) ? $data : trim($data));
}
}
......@@ -841,7 +858,7 @@ class csstidy {
$this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning');
}
}
$previous_property = $this->property;
$this->property = '';
$this->sub_value_arr = array();
$this->value = '';
......@@ -884,7 +901,7 @@ class csstidy {
$this->str_char[] = $string{$i} === '(' ? ')' : $string{$i};
$this->from[] = 'instr';
$this->quoted_string[] = ($_str_char === ')' && $string{$i} !== '(' && trim($_cur_string)==='(')?$_quoted_string:!($string{$i} === '(');
continue;
continue 2;
}
if ($_str_char !== ")" && ($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !$this->escaped($string, $i - 1))) {
......@@ -949,9 +966,15 @@ class csstidy {
$this->status = array_pop($this->from);
$i++;
if ($this->get_cfg('preserve_css_comment')) {
$this->css_add_comment($this->at, $this->selector, $previous_property, $this->status, $cur_comment);
}
$this->_add_token(COMMENT, $cur_comment);
$this->css_add_comment($this->at, $this->selector, $previous_property, $this->status, $cur_comment);
}
if (strlen($cur_comment) > 1 and strncmp($cur_comment, '!', 1) === 0) {
$this->_add_token(IMPORTANT_COMMENT, $cur_comment);
$this->css_add_important_comment($cur_comment);
}
else {
$this->_add_token(COMMENT, $cur_comment);
}
$cur_comment = '';
} else {
$cur_comment .= $string{$i};
......@@ -1040,28 +1063,46 @@ class csstidy {
return!(@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1));
}
/**
* Adds a comment to the existing CSS code
* @param string $media
* @param string $selector
* @param string $property
* @param string $comment
* @access private
* @version 1.2
*/
public function css_add_comment($media, $selector, $property, $status, $comment) {
switch ($status) {
case 'is':
$this->cur_comments[] = trim($comment);
break;
case 'ip':
if (isset($this->css[$media][$selector][$property])) {
$this->comments[$media.$selector.$property][] = trim($comment);
}
break;
default:
break;
}
/**
* Adds a comment to the existing CSS code
* @param string $media
* @param string $selector
* @param string $property
* @param string $comment
* @access private
* @version 1.2
*/
public function css_add_comment($media, $selector, $property, $status, $comment) {
switch ($status) {
case 'is':
$this->cur_comments[] = trim($comment);
break;
case 'ip':
if (isset($this->css[$media][$selector][$property])) {
$this->comments[$media.$selector.$property][] = trim($comment);
}
break;
default:
break;
}
}
/**
* Add an important comment to the css code
* (one we want to keep)
* @param $comment
*/
public function css_add_important_comment($comment) {
if ($this->get_cfg('preserve_css') || trim($comment) == '') {
return;
}
if (!isset($this->css['!'])) {
$this->css['!'] = '';
}
else {
$this->css['!'] .= "\n";
}
$this->css['!'] .= $comment;
}
/**
......@@ -1084,6 +1125,10 @@ class csstidy {
$this->css[$media][$selector][$property] = trim($new_val);
}
} else {
if (!empty($this->cur_comments)) {
$this->comments[$media.$selector] = $this->cur_comments;
$this->cur_comments = array();
}
$this->css[$media][$selector][$property] = trim($new_val);
}
}
......
......@@ -70,40 +70,60 @@ class csstidy_optimise {
* @version 1.0
*/
public function postparse() {
if ($this->parser->get_cfg('reverse_left_and_right') > 0) {
foreach ($this->css as $medium => $selectors) {
if (is_array($selectors)) {
foreach ($selectors as $selector => $properties) {
$this->css[$medium][$selector] = $this->reverse_left_and_right($this->css[$medium][$selector]);
}
}
}
}
if ($this->parser->get_cfg('preserve_css')) {
return;
}
if ((int)$this->parser->get_cfg('merge_selectors') === 2) {
foreach ($this->css as $medium => $value) {
$this->merge_selectors($this->css[$medium]);
if (is_array($value)) {
$this->merge_selectors($this->css[$medium]);
}
}
}
if ($this->parser->get_cfg('discard_invalid_selectors')) {
foreach ($this->css as $medium => $value) {
$this->discard_invalid_selectors($this->css[$medium]);
if (is_array($value)) {
$this->discard_invalid_selectors($this->css[$medium]);
}
}
}
if ($this->parser->get_cfg('optimise_shorthands') > 0) {
foreach ($this->css as $medium => $value) {
foreach ($value as $selector => $value1) {
$this->css[$medium][$selector] = $this->merge_4value_shorthands($this->css[$medium][$selector]);
if (is_array($value)) {
foreach ($value as $selector => $value1) {
$this->css[$medium][$selector] = $this->merge_4value_shorthands($this->css[$medium][$selector]);
$this->css[$medium][$selector] = $this->merge_4value_radius_shorthands($this->css[$medium][$selector]);
if ($this->parser->get_cfg('optimise_shorthands') < 2) {
continue;
}
if ($this->parser->get_cfg('optimise_shorthands') < 2) {
continue;
}
$this->css[$medium][$selector] = $this->merge_font($this->css[$medium][$selector]);
$this->css[$medium][$selector] = $this->merge_font($this->css[$medium][$selector]);
if ($this->parser->get_cfg('optimise_shorthands') < 3) {
continue;
}
if ($this->parser->get_cfg('optimise_shorthands') < 3) {
continue;
}
$this->css[$medium][$selector] = $this->merge_bg($this->css[$medium][$selector]);
if (empty($this->css[$medium][$selector])) {
unset($this->css[$medium][$selector]);
$this->css[$medium][$selector] = $this->merge_bg($this->css[$medium][$selector]);
if (empty($this->css[$medium][$selector])) {
unset($this->css[$medium][$selector]);
}
}
}
}
......@@ -525,12 +545,15 @@ class csstidy_optimise {
* Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;...
* @param string $property
* @param string $value
* @param array|null $shorthands
* @return array
* @version 1.0
* @see merge_4value_shorthands()
*/
public function dissolve_4value_shorthands($property, $value) {
$shorthands = & $this->parser->data['csstidy']['shorthands'];
public function dissolve_4value_shorthands($property, $value, $shorthands = null) {
if (is_null($shorthands)) {
$shorthands = & $this->parser->data['csstidy']['shorthands'];
}
if (!is_array($shorthands[$property])) {
$return[$property] = $value;
return $return;
......@@ -567,25 +590,66 @@ class csstidy_optimise {
return $return;
}
/**
* Dissolves radius properties like
* border-radius:10px 10px 10px / 1px 2px
* to border-top-left:10px 1px;border-top-right:10px 2x;...
* @param string $property
* @param string $value
* @return array
* @version 1.0
* @use dissolve_4value_shorthands()
* @see merge_4value_radius_shorthands()
*/
public function dissolve_4value_radius_shorthands($property, $value) {
$shorthands = & $this->parser->data['csstidy']['radius_shorthands'];
if (!is_array($shorthands[$property])) {
$return[$property] = $value;
return $return;
}
if (strpos($value, '/') !== false) {
$values = $this->explode_ws('/', $value);
if (count($values) == 2) {
$r[0] = $this->dissolve_4value_shorthands($property, trim($values[0]), $shorthands);
$r[1] = $this->dissolve_4value_shorthands($property, trim($values[1]), $shorthands);
$return = array();
foreach ($r[0] as $p=>$v) {
$return[$p] = $v;
if ($r[1][$p] !== $v) {
$return[$p] .= ' ' . $r[1][$p];
}
}
return $return;
}
}
$return = $this->dissolve_4value_shorthands($property, $value, $shorthands);
return $return;
}
/**
* Explodes a string as explode() does, however, not if $sep is escaped or within a string.
* @param string $sep seperator
* @param string $string
* @param bool $explode_in_parenthesis
* @return array
* @version 1.0
*/
public function explode_ws($sep, $string) {
public function explode_ws($sep, $string, $explode_in_parenthesis = false) {
$status = 'st';
$to = '';
$output = array();
$output = array(
0 => '',
);
$num = 0;
for ($i = 0, $len = strlen($string); $i < $len; $i++) {
switch ($status) {
case 'st':
if ($string{$i} == $sep && !$this->parser->escaped($string, $i)) {
++$num;
} elseif ($string{$i} === '"' || $string{$i} === '\'' || $string{$i} === '(' && !$this->parser->escaped($string, $i)) {
} elseif ($string{$i} === '"' || $string{$i} === '\'' || (!$explode_in_parenthesis && $string{$i} === '(') && !$this->parser->escaped($string, $i)) {
$status = 'str';
$to = ($string{$i} === '(') ? ')' : $string{$i};
(isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
......@@ -603,27 +667,26 @@ class csstidy_optimise {
}
}
if (isset($output[0])) {
return $output;
} else {
return array($output);
}
return $output;
}
/**
* Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
* @param array $array
* @param array|null $shorthands
* @return array
* @version 1.2
* @see dissolve_4value_shorthands()
*/
public function merge_4value_shorthands($array) {
public function merge_4value_shorthands($array, $shorthands = null) {
$return = $array;
$shorthands = & $this->parser->data['csstidy']['shorthands'];
if (is_null($shorthands)) {
$shorthands = & $this->parser->data['csstidy']['shorthands'];
}
foreach ($shorthands as $key => $value) {
if (isset($array[$value[0]]) && isset($array[$value[1]])
&& isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) {
if ($value !== 0 && isset($array[$value[0]]) && isset($array[$value[1]])
&& isset($array[$value[2]]) && isset($array[$value[3]])) {
$return[$key] = '';
$important = '';
......@@ -643,6 +706,45 @@ class csstidy_optimise {
return $return;
}
/**
* Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
* @param array $array
* @return array
* @version 1.2
* @use merge_4value_shorthands()
* @see dissolve_4value_radius_shorthands()
*/
public function merge_4value_radius_shorthands($array) {
$return = $array;
$shorthands = & $this->parser->data['csstidy']['radius_shorthands'];
foreach ($shorthands as $key => $value) {
if (isset($array[$value[0]]) && isset($array[$value[1]])
&& isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) {
$return[$key] = '';
$a = array();
for ($i = 0; $i < 4; $i++) {
$v = $this->explode_ws(' ', trim($array[$value[$i]]));
$a[0][$value[$i]] = reset($v);
$a[1][$value[$i]] = end($v);
}
$r = array();
$r[0] = $this->merge_4value_shorthands($a[0], $shorthands);
$r[1] = $this->merge_4value_shorthands($a[1], $shorthands);
if (isset($r[0][$key]) and isset($r[1][$key])) {
$return[$key] = $r[0][$key];
if ($r[1][$key] !== $r[0][$key]) {
$return[$key] .= ' / ' . $r[1][$key];
}
for ($i = 0; $i < 4; $i++) {
unset($return[$value[$i]]);
}
}
}
}
return $return;
}
/**
* Dissolve background property
* @param string $str_value
......@@ -960,4 +1062,237 @@ class csstidy_optimise {
return $input_css;
}
/**
* Reverse left vs right in a list of properties/values
* @param array $array
* @return array
*/
public function reverse_left_and_right($array) {
$return = array();
// change left <-> right in properties name and values
foreach ($array as $propertie => $value) {
if (method_exists($this, $m = 'reverse_left_and_right_' . str_replace('-','_',trim($propertie)))) {
$value = $this->$m($value);
}
// simple replacement for properties
$propertie = str_ireplace(array('left', 'right' ,"\x1"), array("\x1", 'left', 'right') , $propertie);
// be careful for values, not modifying protected or quoted valued
foreach (array('left' => "\x1", 'right' => 'left', "\x1" => 'right') as $v => $r) {
if (strpos($value, $v) !== false) {
// attraper les left et right separes du reste (pas au milieu d'un mot)
if (in_array($v, array('left', 'right') )) {
$value = preg_replace(",\\b$v\\b,", "\x0" , $value);
}
else {
$value = str_replace($v, "\x0" , $value);
}
$value = $this->explode_ws("\x0", $value . ' ', true);
$value = rtrim(implode($r, $value));
$value = str_replace("\x0" , $v, $value);
}
}
$return[$propertie] = $value;
}
return $return;
}
/**
* Reversing 4 values shorthands properties
* @param string $value
* @return string
*/
public function reverse_left_and_right_4value_shorthands($property, $value) {
$shorthands = & $this->parser->data['csstidy']['shorthands'];
if (isset($shorthands[$property])) {
$property_right = $shorthands[$property][1];
$property_left = $shorthands[$property][3];
$v = $this->dissolve_4value_shorthands($property, $value);
if ($v[$property_left] !== $v[$property_right]) {
$r = $v[$property_right];
$v[$property_right] = $v[$property_left];
$v[$property_left] = $r;
$v = $this->merge_4value_shorthands($v);
if (isset($v[$property])) {
return $v[$property];
}
}
}
return $value;
}
/**
* Reversing 4 values radius shorthands properties
* @param string $value
* @return string
*/
public function reverse_left_and_right_4value_radius_shorthands($property, $value) {
$shorthands = & $this->parser->data['csstidy']['radius_shorthands'];
if (isset($shorthands[$property])) {
$v = $this->dissolve_4value_radius_shorthands($property, $value);
if ($v[$shorthands[$property][0]] !== $v[$shorthands[$property][1]]
or $v[$shorthands[$property][2]] !== $v[$shorthands[$property][3]]) {
$r = array(
$shorthands[$property][0] => $v[$shorthands[$property][1]],
$shorthands[$property][1] => $v[$shorthands[$property][0]],
$shorthands[$property][2] => $v[$shorthands[$property][3]],
$shorthands[$property][3] => $v[$shorthands[$property][2]],
);
$v = $this->merge_4value_radius_shorthands($r);
if (isset($v[$property])) {
return $v[$property];
}
}
}
return $value;
}
/**
* Reversing margin shorthands
* @param string $value
* @return string
*/
public function reverse_left_and_right_margin($value) {
return $this->reverse_left_and_right_4value_shorthands('margin', $value);
}
/**
* Reversing padding shorthands
* @param string $value
* @return string
*/
public function reverse_left_and_right_padding($value) {
return $this->reverse_left_and_right_4value_shorthands('padding', $value);
}
/**
* Reversing border-color shorthands
* @param string $value
* @return string
*/
public function reverse_left_and_right_border_color($value) {
return $this->reverse_left_and_right_4value_shorthands('border-color', $value);
}
/**
* Reversing border-style shorthands
* @param string $value
* @return string
*/
public function reverse_left_and_right_border_style($value) {