]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/HTMLPurifier/standalone/HTMLPurifier/Filter/ExtractStyleBlocks.php
Release 6.5.0
[Github/sugarcrm.git] / include / HTMLPurifier / standalone / HTMLPurifier / Filter / ExtractStyleBlocks.php
1 <?php
2
3 /**
4  * This filter extracts <style> blocks from input HTML, cleans them up
5  * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks')
6  * so they can be used elsewhere in the document.
7  *
8  * @note
9  *      See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for
10  *      sample usage.
11  *
12  * @note
13  *      This filter can also be used on stylesheets not included in the
14  *      document--something purists would probably prefer. Just directly
15  *      call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS()
16  */
17 class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
18 {
19
20     public $name = 'ExtractStyleBlocks';
21     private $_styleMatches = array();
22     private $_tidy;
23
24     public function __construct() {
25         $this->_tidy = new csstidy();
26     }
27
28     /**
29      * Save the contents of CSS blocks to style matches
30      * @param $matches preg_replace style $matches array
31      */
32     protected function styleCallback($matches) {
33         $this->_styleMatches[] = $matches[1];
34     }
35
36     /**
37      * Removes inline <style> tags from HTML, saves them for later use
38      * @todo Extend to indicate non-text/css style blocks
39      */
40     public function preFilter($html, $config, $context) {
41         $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl');
42         if ($tidy !== null) $this->_tidy = $tidy;
43         $html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html);
44         $style_blocks = $this->_styleMatches;
45         $this->_styleMatches = array(); // reset
46         $context->register('StyleBlocks', $style_blocks); // $context must not be reused
47         if ($this->_tidy) {
48             foreach ($style_blocks as &$style) {
49                 $style = $this->cleanCSS($style, $config, $context);
50             }
51         }
52         return $html;
53     }
54
55     /**
56      * Takes CSS (the stuff found in <style>) and cleans it.
57      * @warning Requires CSSTidy <http://csstidy.sourceforge.net/>
58      * @param $css     CSS styling to clean
59      * @param $config  Instance of HTMLPurifier_Config
60      * @param $context Instance of HTMLPurifier_Context
61      * @return Cleaned CSS
62      */
63     public function cleanCSS($css, $config, $context) {
64         // prepare scope
65         $scope = $config->get('Filter.ExtractStyleBlocks.Scope');
66         if ($scope !== null) {
67             $scopes = array_map('trim', explode(',', $scope));
68         } else {
69             $scopes = array();
70         }
71         // remove comments from CSS
72         $css = trim($css);
73         if (strncmp('<!--', $css, 4) === 0) {
74             $css = substr($css, 4);
75         }
76         if (strlen($css) > 3 && substr($css, -3) == '-->') {
77             $css = substr($css, 0, -3);
78         }
79         $css = trim($css);
80         $this->_tidy->parse($css);
81         $css_definition = $config->getDefinition('CSS');
82         foreach ($this->_tidy->css as $k => $decls) {
83             // $decls are all CSS declarations inside an @ selector
84             $new_decls = array();
85             foreach ($decls as $selector => $style) {
86                 $selector = trim($selector);
87                 if ($selector === '') continue; // should not happen
88                 if ($selector[0] === '+') {
89                     if ($selector !== '' && $selector[0] === '+') continue;
90                 }
91                 if (!empty($scopes)) {
92                     $new_selector = array(); // because multiple ones are possible
93                     $selectors = array_map('trim', explode(',', $selector));
94                     foreach ($scopes as $s1) {
95                         foreach ($selectors as $s2) {
96                             $new_selector[] = "$s1 $s2";
97                         }
98                     }
99                     $selector = implode(', ', $new_selector); // now it's a string
100                 }
101                 foreach ($style as $name => $value) {
102                     if (!isset($css_definition->info[$name])) {
103                         unset($style[$name]);
104                         continue;
105                     }
106                     $def = $css_definition->info[$name];
107                     $ret = $def->validate($value, $config, $context);
108                     if ($ret === false) unset($style[$name]);
109                     else $style[$name] = $ret;
110                 }
111                 $new_decls[$selector] = $style;
112             }
113             $this->_tidy->css[$k] = $new_decls;
114         }
115         // remove stuff that shouldn't be used, could be reenabled
116         // after security risks are analyzed
117         $this->_tidy->import = array();
118         $this->_tidy->charset = null;
119         $this->_tidy->namespace = null;
120         $css = $this->_tidy->print->plain();
121         // we are going to escape any special characters <>& to ensure
122         // that no funny business occurs (i.e. </style> in a font-family prop).
123         if ($config->get('Filter.ExtractStyleBlocks.Escaping')) {
124             $css = str_replace(
125                 array('<',    '>',    '&'),
126                 array('\3C ', '\3E ', '\26 '),
127                 $css
128             );
129         }
130         return $css;
131     }
132
133 }
134
135 // vim: et sw=4 sts=4