1: | <?php
|
2: |
|
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | |
25: |
|
26: | class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
|
27: | {
|
28: |
|
29: |
|
30: |
|
31: | |
32: | |
33: | |
34: |
|
35: | public $info = array();
|
36: |
|
37: | |
38: | |
39: | |
40: |
|
41: | public $info_global_attr = array();
|
42: |
|
43: | |
44: | |
45: | |
46: |
|
47: | public $info_parent = 'div';
|
48: |
|
49: | |
50: | |
51: | |
52: | |
53: |
|
54: | public $info_parent_def;
|
55: |
|
56: | |
57: | |
58: | |
59: | |
60: |
|
61: | public $info_block_wrapper = 'p';
|
62: |
|
63: | |
64: | |
65: | |
66: |
|
67: | public $info_tag_transform = array();
|
68: |
|
69: | |
70: | |
71: | |
72: |
|
73: | public $info_attr_transform_pre = array();
|
74: |
|
75: | |
76: | |
77: | |
78: |
|
79: | public $info_attr_transform_post = array();
|
80: |
|
81: | |
82: | |
83: | |
84: | |
85: |
|
86: | public $info_content_sets = array();
|
87: |
|
88: | |
89: | |
90: | |
91: |
|
92: | public $info_injector = array();
|
93: |
|
94: | |
95: | |
96: | |
97: |
|
98: | public $doctype;
|
99: |
|
100: |
|
101: |
|
102: |
|
103: |
|
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | |
111: | |
112: |
|
113: | public function addAttribute($element_name, $attr_name, $def)
|
114: | {
|
115: | $module = $this->getAnonymousModule();
|
116: | if (!isset($module->info[$element_name])) {
|
117: | $element = $module->addBlankElement($element_name);
|
118: | } else {
|
119: | $element = $module->info[$element_name];
|
120: | }
|
121: | $element->attr[$attr_name] = $def;
|
122: | }
|
123: |
|
124: | |
125: | |
126: | |
127: | |
128: |
|
129: | public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
|
130: | {
|
131: | $module = $this->getAnonymousModule();
|
132: |
|
133: |
|
134: | $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
|
135: | return $element;
|
136: | }
|
137: |
|
138: | |
139: | |
140: | |
141: | |
142: | |
143: | |
144: | |
145: |
|
146: | public function addBlankElement($element_name)
|
147: | {
|
148: | $module = $this->getAnonymousModule();
|
149: | $element = $module->addBlankElement($element_name);
|
150: | return $element;
|
151: | }
|
152: |
|
153: | |
154: | |
155: | |
156: | |
157: | |
158: |
|
159: | public function getAnonymousModule()
|
160: | {
|
161: | if (!$this->_anonModule) {
|
162: | $this->_anonModule = new HTMLPurifier_HTMLModule();
|
163: | $this->_anonModule->name = 'Anonymous';
|
164: | }
|
165: | return $this->_anonModule;
|
166: | }
|
167: |
|
168: | private $_anonModule = null;
|
169: |
|
170: |
|
171: |
|
172: | |
173: | |
174: |
|
175: | public $type = 'HTML';
|
176: |
|
177: | |
178: | |
179: |
|
180: | public $manager;
|
181: |
|
182: | |
183: | |
184: |
|
185: | public function __construct()
|
186: | {
|
187: | $this->manager = new HTMLPurifier_HTMLModuleManager();
|
188: | }
|
189: |
|
190: | |
191: | |
192: |
|
193: | protected function doSetup($config)
|
194: | {
|
195: | $this->processModules($config);
|
196: | $this->setupConfigStuff($config);
|
197: | unset($this->manager);
|
198: |
|
199: |
|
200: | foreach ($this->info as $k => $v) {
|
201: | unset($this->info[$k]->content_model);
|
202: | unset($this->info[$k]->content_model_type);
|
203: | }
|
204: | }
|
205: |
|
206: | |
207: | |
208: | |
209: |
|
210: | protected function processModules($config)
|
211: | {
|
212: | if ($this->_anonModule) {
|
213: |
|
214: |
|
215: |
|
216: | $this->manager->addModule($this->_anonModule);
|
217: | unset($this->_anonModule);
|
218: | }
|
219: |
|
220: | $this->manager->setup($config);
|
221: | $this->doctype = $this->manager->doctype;
|
222: |
|
223: | foreach ($this->manager->modules as $module) {
|
224: | foreach ($module->info_tag_transform as $k => $v) {
|
225: | if ($v === false) {
|
226: | unset($this->info_tag_transform[$k]);
|
227: | } else {
|
228: | $this->info_tag_transform[$k] = $v;
|
229: | }
|
230: | }
|
231: | foreach ($module->info_attr_transform_pre as $k => $v) {
|
232: | if ($v === false) {
|
233: | unset($this->info_attr_transform_pre[$k]);
|
234: | } else {
|
235: | $this->info_attr_transform_pre[$k] = $v;
|
236: | }
|
237: | }
|
238: | foreach ($module->info_attr_transform_post as $k => $v) {
|
239: | if ($v === false) {
|
240: | unset($this->info_attr_transform_post[$k]);
|
241: | } else {
|
242: | $this->info_attr_transform_post[$k] = $v;
|
243: | }
|
244: | }
|
245: | foreach ($module->info_injector as $k => $v) {
|
246: | if ($v === false) {
|
247: | unset($this->info_injector[$k]);
|
248: | } else {
|
249: | $this->info_injector[$k] = $v;
|
250: | }
|
251: | }
|
252: | }
|
253: | $this->info = $this->manager->getElements();
|
254: | $this->info_content_sets = $this->manager->contentSets->lookup;
|
255: | }
|
256: |
|
257: | |
258: | |
259: | |
260: |
|
261: | protected function setupConfigStuff($config)
|
262: | {
|
263: | $block_wrapper = $config->get('HTML.BlockWrapper');
|
264: | if (isset($this->info_content_sets['Block'][$block_wrapper])) {
|
265: | $this->info_block_wrapper = $block_wrapper;
|
266: | } else {
|
267: | trigger_error(
|
268: | 'Cannot use non-block element as block wrapper',
|
269: | E_USER_ERROR
|
270: | );
|
271: | }
|
272: |
|
273: | $parent = $config->get('HTML.Parent');
|
274: | $def = $this->manager->getElement($parent, true);
|
275: | if ($def) {
|
276: | $this->info_parent = $parent;
|
277: | $this->info_parent_def = $def;
|
278: | } else {
|
279: | trigger_error(
|
280: | 'Cannot use unrecognized element as parent',
|
281: | E_USER_ERROR
|
282: | );
|
283: | $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
|
284: | }
|
285: |
|
286: |
|
287: | $support = "(for information on implementing this, see the support forums) ";
|
288: |
|
289: |
|
290: |
|
291: | $allowed_elements = $config->get('HTML.AllowedElements');
|
292: | $allowed_attributes = $config->get('HTML.AllowedAttributes');
|
293: |
|
294: | if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
|
295: | $allowed = $config->get('HTML.Allowed');
|
296: | if (is_string($allowed)) {
|
297: | list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
|
298: | }
|
299: | }
|
300: |
|
301: | if (is_array($allowed_elements)) {
|
302: | foreach ($this->info as $name => $d) {
|
303: | if (!isset($allowed_elements[$name])) {
|
304: | unset($this->info[$name]);
|
305: | }
|
306: | unset($allowed_elements[$name]);
|
307: | }
|
308: |
|
309: | foreach ($allowed_elements as $element => $d) {
|
310: | $element = htmlspecialchars($element);
|
311: | trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
|
312: | }
|
313: | }
|
314: |
|
315: |
|
316: |
|
317: | $allowed_attributes_mutable = $allowed_attributes;
|
318: | if (is_array($allowed_attributes)) {
|
319: |
|
320: |
|
321: |
|
322: | foreach ($this->info_global_attr as $attr => $x) {
|
323: | $keys = array($attr, "*@$attr", "*.$attr");
|
324: | $delete = true;
|
325: | foreach ($keys as $key) {
|
326: | if ($delete && isset($allowed_attributes[$key])) {
|
327: | $delete = false;
|
328: | }
|
329: | if (isset($allowed_attributes_mutable[$key])) {
|
330: | unset($allowed_attributes_mutable[$key]);
|
331: | }
|
332: | }
|
333: | if ($delete) {
|
334: | unset($this->info_global_attr[$attr]);
|
335: | }
|
336: | }
|
337: |
|
338: | foreach ($this->info as $tag => $info) {
|
339: | foreach ($info->attr as $attr => $x) {
|
340: | $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
|
341: | $delete = true;
|
342: | foreach ($keys as $key) {
|
343: | if ($delete && isset($allowed_attributes[$key])) {
|
344: | $delete = false;
|
345: | }
|
346: | if (isset($allowed_attributes_mutable[$key])) {
|
347: | unset($allowed_attributes_mutable[$key]);
|
348: | }
|
349: | }
|
350: | if ($delete) {
|
351: | if ($this->info[$tag]->attr[$attr]->required) {
|
352: | trigger_error(
|
353: | "Required attribute '$attr' in element '$tag' " .
|
354: | "was not allowed, which means '$tag' will not be allowed either",
|
355: | E_USER_WARNING
|
356: | );
|
357: | }
|
358: | unset($this->info[$tag]->attr[$attr]);
|
359: | }
|
360: | }
|
361: | }
|
362: |
|
363: | foreach ($allowed_attributes_mutable as $elattr => $d) {
|
364: | $bits = preg_split('/[.@]/', $elattr, 2);
|
365: | $c = count($bits);
|
366: | switch ($c) {
|
367: | case 2:
|
368: | if ($bits[0] !== '*') {
|
369: | $element = htmlspecialchars($bits[0]);
|
370: | $attribute = htmlspecialchars($bits[1]);
|
371: | if (!isset($this->info[$element])) {
|
372: | trigger_error(
|
373: | "Cannot allow attribute '$attribute' if element " .
|
374: | "'$element' is not allowed/supported $support"
|
375: | );
|
376: | } else {
|
377: | trigger_error(
|
378: | "Attribute '$attribute' in element '$element' not supported $support",
|
379: | E_USER_WARNING
|
380: | );
|
381: | }
|
382: | break;
|
383: | }
|
384: |
|
385: | case 1:
|
386: | $attribute = htmlspecialchars($bits[0]);
|
387: | trigger_error(
|
388: | "Global attribute '$attribute' is not ".
|
389: | "supported in any elements $support",
|
390: | E_USER_WARNING
|
391: | );
|
392: | break;
|
393: | }
|
394: | }
|
395: | }
|
396: |
|
397: |
|
398: |
|
399: | $forbidden_elements = $config->get('HTML.ForbiddenElements');
|
400: | $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
|
401: |
|
402: | foreach ($this->info as $tag => $info) {
|
403: | if (isset($forbidden_elements[$tag])) {
|
404: | unset($this->info[$tag]);
|
405: | continue;
|
406: | }
|
407: | foreach ($info->attr as $attr => $x) {
|
408: | if (isset($forbidden_attributes["$tag@$attr"]) ||
|
409: | isset($forbidden_attributes["*@$attr"]) ||
|
410: | isset($forbidden_attributes[$attr])
|
411: | ) {
|
412: | unset($this->info[$tag]->attr[$attr]);
|
413: | continue;
|
414: | } elseif (isset($forbidden_attributes["$tag.$attr"])) {
|
415: |
|
416: | trigger_error(
|
417: | "Error with $tag.$attr: tag.attr syntax not supported for " .
|
418: | "HTML.ForbiddenAttributes; use tag@attr instead",
|
419: | E_USER_WARNING
|
420: | );
|
421: | }
|
422: | }
|
423: | }
|
424: | foreach ($forbidden_attributes as $key => $v) {
|
425: | if (strlen($key) < 2) {
|
426: | continue;
|
427: | }
|
428: | if ($key[0] != '*') {
|
429: | continue;
|
430: | }
|
431: | if ($key[1] == '.') {
|
432: | trigger_error(
|
433: | "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
|
434: | E_USER_WARNING
|
435: | );
|
436: | }
|
437: | }
|
438: |
|
439: |
|
440: | foreach ($this->info_injector as $i => $injector) {
|
441: | if ($injector->checkNeeded($config) !== false) {
|
442: |
|
443: |
|
444: | unset($this->info_injector[$i]);
|
445: | }
|
446: | }
|
447: | }
|
448: |
|
449: | |
450: | |
451: | |
452: | |
453: | |
454: | |
455: | |
456: | |
457: |
|
458: | public function parseTinyMCEAllowedList($list)
|
459: | {
|
460: | $list = str_replace(array(' ', "\t"), '', $list);
|
461: |
|
462: | $elements = array();
|
463: | $attributes = array();
|
464: |
|
465: | $chunks = preg_split('/(,|[\n\r]+)/', $list);
|
466: | foreach ($chunks as $chunk) {
|
467: | if (empty($chunk)) {
|
468: | continue;
|
469: | }
|
470: |
|
471: | if (!strpos($chunk, '[')) {
|
472: | $element = $chunk;
|
473: | $attr = false;
|
474: | } else {
|
475: | list($element, $attr) = explode('[', $chunk);
|
476: | }
|
477: | if ($element !== '*') {
|
478: | $elements[$element] = true;
|
479: | }
|
480: | if (!$attr) {
|
481: | continue;
|
482: | }
|
483: | $attr = substr($attr, 0, strlen($attr) - 1);
|
484: | $attr = explode('|', $attr);
|
485: | foreach ($attr as $key) {
|
486: | $attributes["$element.$key"] = true;
|
487: | }
|
488: | }
|
489: | return array($elements, $attributes);
|
490: | }
|
491: | }
|
492: |
|
493: |
|
494: | |