]> git.immae.eu Git - github/wallabag/wallabag.git/blob - inc/3rdparty/htmlpurifier/HTMLPurifier/HTMLDefinition.php
[add] HTML Purifier added to clean code
[github/wallabag/wallabag.git] / inc / 3rdparty / htmlpurifier / HTMLPurifier / HTMLDefinition.php
1 <?php
2
3 /**
4 * Definition of the purified HTML that describes allowed children,
5 * attributes, and many other things.
6 *
7 * Conventions:
8 *
9 * All member variables that are prefixed with info
10 * (including the main $info array) are used by HTML Purifier internals
11 * and should not be directly edited when customizing the HTMLDefinition.
12 * They can usually be set via configuration directives or custom
13 * modules.
14 *
15 * On the other hand, member variables without the info prefix are used
16 * internally by the HTMLDefinition and MUST NOT be used by other HTML
17 * Purifier internals. Many of them, however, are public, and may be
18 * edited by userspace code to tweak the behavior of HTMLDefinition.
19 *
20 * @note This class is inspected by Printer_HTMLDefinition; please
21 * update that class if things here change.
22 *
23 * @warning Directives that change this object's structure must be in
24 * the HTML or Attr namespace!
25 */
26 class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
27 {
28
29 // FULLY-PUBLIC VARIABLES ---------------------------------------------
30
31 /**
32 * Associative array of element names to HTMLPurifier_ElementDef.
33 * @type HTMLPurifier_ElementDef[]
34 */
35 public $info = array();
36
37 /**
38 * Associative array of global attribute name to attribute definition.
39 * @type array
40 */
41 public $info_global_attr = array();
42
43 /**
44 * String name of parent element HTML will be going into.
45 * @type string
46 */
47 public $info_parent = 'div';
48
49 /**
50 * Definition for parent element, allows parent element to be a
51 * tag that's not allowed inside the HTML fragment.
52 * @type HTMLPurifier_ElementDef
53 */
54 public $info_parent_def;
55
56 /**
57 * String name of element used to wrap inline elements in block context.
58 * @type string
59 * @note This is rarely used except for BLOCKQUOTEs in strict mode
60 */
61 public $info_block_wrapper = 'p';
62
63 /**
64 * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
65 * @type array
66 */
67 public $info_tag_transform = array();
68
69 /**
70 * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
71 * @type HTMLPurifier_AttrTransform[]
72 */
73 public $info_attr_transform_pre = array();
74
75 /**
76 * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
77 * @type HTMLPurifier_AttrTransform[]
78 */
79 public $info_attr_transform_post = array();
80
81 /**
82 * Nested lookup array of content set name (Block, Inline) to
83 * element name to whether or not it belongs in that content set.
84 * @type array
85 */
86 public $info_content_sets = array();
87
88 /**
89 * Indexed list of HTMLPurifier_Injector to be used.
90 * @type HTMLPurifier_Injector[]
91 */
92 public $info_injector = array();
93
94 /**
95 * Doctype object
96 * @type HTMLPurifier_Doctype
97 */
98 public $doctype;
99
100
101
102 // RAW CUSTOMIZATION STUFF --------------------------------------------
103
104 /**
105 * Adds a custom attribute to a pre-existing element
106 * @note This is strictly convenience, and does not have a corresponding
107 * method in HTMLPurifier_HTMLModule
108 * @param string $element_name Element name to add attribute to
109 * @param string $attr_name Name of attribute
110 * @param mixed $def Attribute definition, can be string or object, see
111 * HTMLPurifier_AttrTypes for details
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 * Adds a custom element to your HTML definition
126 * @see HTMLPurifier_HTMLModule::addElement() for detailed
127 * parameter and return value descriptions.
128 */
129 public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
130 {
131 $module = $this->getAnonymousModule();
132 // assume that if the user is calling this, the element
133 // is safe. This may not be a good idea
134 $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
135 return $element;
136 }
137
138 /**
139 * Adds a blank element to your HTML definition, for overriding
140 * existing behavior
141 * @param string $element_name
142 * @return HTMLPurifier_ElementDef
143 * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
144 * parameter and return value descriptions.
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 * Retrieves a reference to the anonymous module, so you can
155 * bust out advanced features without having to make your own
156 * module.
157 * @return HTMLPurifier_HTMLModule
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 // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
171
172 /**
173 * @type string
174 */
175 public $type = 'HTML';
176
177 /**
178 * @type HTMLPurifier_HTMLModuleManager
179 */
180 public $manager;
181
182 /**
183 * Performs low-cost, preliminary initialization.
184 */
185 public function __construct()
186 {
187 $this->manager = new HTMLPurifier_HTMLModuleManager();
188 }
189
190 /**
191 * @param HTMLPurifier_Config $config
192 */
193 protected function doSetup($config)
194 {
195 $this->processModules($config);
196 $this->setupConfigStuff($config);
197 unset($this->manager);
198
199 // cleanup some of the element definitions
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 * Extract out the information from the manager
208 * @param HTMLPurifier_Config $config
209 */
210 protected function processModules($config)
211 {
212 if ($this->_anonModule) {
213 // for user specific changes
214 // this is late-loaded so we don't have to deal with PHP4
215 // reference wonky-ness
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 * Sets up stuff based on config. We need a better way of doing this.
259 * @param HTMLPurifier_Config $config
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 // support template text
287 $support = "(for information on implementing this, see the support forums) ";
288
289 // setup allowed elements -----------------------------------------
290
291 $allowed_elements = $config->get('HTML.AllowedElements');
292 $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
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 // emit errors
309 foreach ($allowed_elements as $element => $d) {
310 $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
311 trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
312 }
313 }
314
315 // setup allowed attributes ---------------------------------------
316
317 $allowed_attributes_mutable = $allowed_attributes; // by copy!
318 if (is_array($allowed_attributes)) {
319 // This actually doesn't do anything, since we went away from
320 // global attributes. It's possible that userland code uses
321 // it, but HTMLModuleManager doesn't!
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 // emit errors
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 // otherwise fall through
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 // setup forbidden elements ---------------------------------------
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"])) { // this segment might get removed eventually
415 // $tag.$attr are not user supplied, so no worries!
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 // setup injectors -----------------------------------------------------
440 foreach ($this->info_injector as $i => $injector) {
441 if ($injector->checkNeeded($config) !== false) {
442 // remove injector that does not have it's required
443 // elements/attributes present, and is thus not needed.
444 unset($this->info_injector[$i]);
445 }
446 }
447 }
448
449 /**
450 * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
451 * separate lists for processing. Format is element[attr1|attr2],element2...
452 * @warning Although it's largely drawn from TinyMCE's implementation,
453 * it is different, and you'll probably have to modify your lists
454 * @param array $list String list to parse
455 * @return array
456 * @todo Give this its own class, probably static interface
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 // remove TinyMCE element control characters
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); // remove trailing ]
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 // vim: et sw=4 sts=4