| 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: | |