1: | <?php
|
2: |
|
3: |
|
4: |
|
5: |
|
6: |
|
7: | function htmlpurifier_filter_extractstyleblocks_muteerrorhandler()
|
8: | {
|
9: | }
|
10: |
|
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
24: |
|
25: | class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
|
26: | {
|
27: | |
28: | |
29: |
|
30: | public $name = 'ExtractStyleBlocks';
|
31: |
|
32: | |
33: | |
34: |
|
35: | private $_styleMatches = array();
|
36: |
|
37: | |
38: | |
39: |
|
40: | private $_tidy;
|
41: |
|
42: | |
43: | |
44: |
|
45: | private $_id_attrdef;
|
46: |
|
47: | |
48: | |
49: |
|
50: | private $_class_attrdef;
|
51: |
|
52: | |
53: | |
54: |
|
55: | private $_enum_attrdef;
|
56: |
|
57: | public function __construct()
|
58: | {
|
59: | $this->_tidy = new csstidy();
|
60: | $this->_tidy->set_cfg('lowercase_s', false);
|
61: | $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true);
|
62: | $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident();
|
63: | $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum(
|
64: | array(
|
65: | 'first-child',
|
66: | 'link',
|
67: | 'visited',
|
68: | 'active',
|
69: | 'hover',
|
70: | 'focus'
|
71: | )
|
72: | );
|
73: | }
|
74: |
|
75: | |
76: | |
77: | |
78: |
|
79: | protected function styleCallback($matches)
|
80: | {
|
81: | $this->_styleMatches[] = $matches[1];
|
82: | }
|
83: |
|
84: | |
85: | |
86: | |
87: | |
88: | |
89: | |
90: | |
91: |
|
92: | public function preFilter($html, $config, $context)
|
93: | {
|
94: | $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl');
|
95: | if ($tidy !== null) {
|
96: | $this->_tidy = $tidy;
|
97: | }
|
98: |
|
99: |
|
100: |
|
101: | $html = preg_replace_callback('#<style(?:\s.*)?>(.*)<\/style>#isU', array($this, 'styleCallback'), $html);
|
102: | $style_blocks = $this->_styleMatches;
|
103: | $this->_styleMatches = array();
|
104: | $context->register('StyleBlocks', $style_blocks);
|
105: | if ($this->_tidy) {
|
106: | foreach ($style_blocks as &$style) {
|
107: | $style = $this->cleanCSS($style, $config, $context);
|
108: | }
|
109: | }
|
110: | return $html;
|
111: | }
|
112: |
|
113: | |
114: | |
115: | |
116: | |
117: | |
118: | |
119: | |
120: | |
121: |
|
122: | public function cleanCSS($css, $config, $context)
|
123: | {
|
124: |
|
125: | $scope = $config->get('Filter.ExtractStyleBlocks.Scope');
|
126: | if ($scope !== null) {
|
127: | $scopes = array_map('trim', explode(',', $scope));
|
128: | } else {
|
129: | $scopes = array();
|
130: | }
|
131: |
|
132: | $css = trim($css);
|
133: | if (strncmp('<!--', $css, 4) === 0) {
|
134: | $css = substr($css, 4);
|
135: | }
|
136: | if (strlen($css) > 3 && substr($css, -3) == '-->') {
|
137: | $css = substr($css, 0, -3);
|
138: | }
|
139: | $css = trim($css);
|
140: | set_error_handler('htmlpurifier_filter_extractstyleblocks_muteerrorhandler');
|
141: | $this->_tidy->parse($css);
|
142: | restore_error_handler();
|
143: | $css_definition = $config->getDefinition('CSS');
|
144: | $html_definition = $config->getDefinition('HTML');
|
145: | $new_css = array();
|
146: | foreach ($this->_tidy->css as $k => $decls) {
|
147: |
|
148: | $new_decls = array();
|
149: | foreach ($decls as $selector => $style) {
|
150: | $selector = trim($selector);
|
151: | if ($selector === '') {
|
152: | continue;
|
153: | }
|
154: |
|
155: |
|
156: |
|
157: |
|
158: |
|
159: |
|
160: |
|
161: |
|
162: |
|
163: |
|
164: |
|
165: |
|
166: |
|
167: |
|
168: |
|
169: |
|
170: |
|
171: |
|
172: |
|
173: |
|
174: |
|
175: |
|
176: |
|
177: |
|
178: |
|
179: |
|
180: |
|
181: |
|
182: |
|
183: |
|
184: |
|
185: |
|
186: |
|
187: |
|
188: |
|
189: |
|
190: |
|
191: |
|
192: |
|
193: |
|
194: |
|
195: |
|
196: |
|
197: |
|
198: |
|
199: |
|
200: |
|
201: |
|
202: |
|
203: |
|
204: |
|
205: |
|
206: |
|
207: |
|
208: |
|
209: |
|
210: |
|
211: |
|
212: |
|
213: |
|
214: |
|
215: |
|
216: | $selectors = array_map('trim', explode(',', $selector));
|
217: | $new_selectors = array();
|
218: | foreach ($selectors as $sel) {
|
219: |
|
220: | $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE);
|
221: |
|
222: |
|
223: | $nsel = null;
|
224: | $delim = null;
|
225: |
|
226: | for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) {
|
227: | $x = $basic_selectors[$i];
|
228: | if ($i % 2) {
|
229: |
|
230: | if ($x === ' ') {
|
231: | $delim = ' ';
|
232: | } else {
|
233: | $delim = ' ' . $x . ' ';
|
234: | }
|
235: | } else {
|
236: |
|
237: | $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE);
|
238: | $sdelim = null;
|
239: | $nx = null;
|
240: | for ($j = 0, $cc = count($components); $j < $cc; $j++) {
|
241: | $y = $components[$j];
|
242: | if ($j === 0) {
|
243: | if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) {
|
244: | $nx = $y;
|
245: | } else {
|
246: |
|
247: |
|
248: |
|
249: |
|
250: |
|
251: | }
|
252: | } elseif ($j % 2) {
|
253: |
|
254: | $sdelim = $y;
|
255: | } else {
|
256: | $attrdef = null;
|
257: | if ($sdelim === '#') {
|
258: | $attrdef = $this->_id_attrdef;
|
259: | } elseif ($sdelim === '.') {
|
260: | $attrdef = $this->_class_attrdef;
|
261: | } elseif ($sdelim === ':') {
|
262: | $attrdef = $this->_enum_attrdef;
|
263: | } else {
|
264: | throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split');
|
265: | }
|
266: | $r = $attrdef->validate($y, $config, $context);
|
267: | if ($r !== false) {
|
268: | if ($r !== true) {
|
269: | $y = $r;
|
270: | }
|
271: | if ($nx === null) {
|
272: | $nx = '';
|
273: | }
|
274: | $nx .= $sdelim . $y;
|
275: | }
|
276: | }
|
277: | }
|
278: | if ($nx !== null) {
|
279: | if ($nsel === null) {
|
280: | $nsel = $nx;
|
281: | } else {
|
282: | $nsel .= $delim . $nx;
|
283: | }
|
284: | } else {
|
285: |
|
286: |
|
287: | }
|
288: | }
|
289: | }
|
290: | if ($nsel !== null) {
|
291: | if (!empty($scopes)) {
|
292: | foreach ($scopes as $s) {
|
293: | $new_selectors[] = "$s $nsel";
|
294: | }
|
295: | } else {
|
296: | $new_selectors[] = $nsel;
|
297: | }
|
298: | }
|
299: | }
|
300: | if (empty($new_selectors)) {
|
301: | continue;
|
302: | }
|
303: | $selector = implode(', ', $new_selectors);
|
304: | foreach ($style as $name => $value) {
|
305: | if (!isset($css_definition->info[$name])) {
|
306: | unset($style[$name]);
|
307: | continue;
|
308: | }
|
309: | $def = $css_definition->info[$name];
|
310: | $ret = $def->validate($value, $config, $context);
|
311: | if ($ret === false) {
|
312: | unset($style[$name]);
|
313: | } else {
|
314: | $style[$name] = $ret;
|
315: | }
|
316: | }
|
317: | $new_decls[$selector] = $style;
|
318: | }
|
319: | $new_css[$k] = $new_decls;
|
320: | }
|
321: |
|
322: |
|
323: | $this->_tidy->css = $new_css;
|
324: | $this->_tidy->import = array();
|
325: | $this->_tidy->charset = null;
|
326: | $this->_tidy->namespace = null;
|
327: | $css = $this->_tidy->print->plain();
|
328: |
|
329: |
|
330: | if ($config->get('Filter.ExtractStyleBlocks.Escaping')) {
|
331: | $css = str_replace(
|
332: | array('<', '>', '&'),
|
333: | array('\3C ', '\3E ', '\26 '),
|
334: | $css
|
335: | );
|
336: | }
|
337: | return $css;
|
338: | }
|
339: | }
|
340: |
|
341: |
|
342: | |