4 * Definition of the purified HTML that describes allowed children,
5 * attributes, and many other things.
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
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.
20 * @note This class is inspected by Printer_HTMLDefinition; please
21 * update that class if things here change.
23 * @warning Directives that change this object's structure must be in
24 * the HTML or Attr namespace!
26 class HTMLPurifier_HTMLDefinition
extends HTMLPurifier_Definition
29 // FULLY-PUBLIC VARIABLES ---------------------------------------------
32 * Associative array of element names to HTMLPurifier_ElementDef.
33 * @type HTMLPurifier_ElementDef[]
35 public $info = array();
38 * Associative array of global attribute name to attribute definition.
41 public $info_global_attr = array();
44 * String name of parent element HTML will be going into.
47 public $info_parent = 'div';
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
54 public $info_parent_def;
57 * String name of element used to wrap inline elements in block context.
59 * @note This is rarely used except for BLOCKQUOTEs in strict mode
61 public $info_block_wrapper = 'p';
64 * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
67 public $info_tag_transform = array();
70 * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
71 * @type HTMLPurifier_AttrTransform[]
73 public $info_attr_transform_pre = array();
76 * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
77 * @type HTMLPurifier_AttrTransform[]
79 public $info_attr_transform_post = array();
82 * Nested lookup array of content set name (Block, Inline) to
83 * element name to whether or not it belongs in that content set.
86 public $info_content_sets = array();
89 * Indexed list of HTMLPurifier_Injector to be used.
90 * @type HTMLPurifier_Injector[]
92 public $info_injector = array();
96 * @type HTMLPurifier_Doctype
102 // RAW CUSTOMIZATION STUFF --------------------------------------------
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
113 public function addAttribute($element_name, $attr_name, $def)
115 $module = $this->getAnonymousModule();
116 if (!isset($module->info
[$element_name])) {
117 $element = $module->addBlankElement($element_name);
119 $element = $module->info
[$element_name];
121 $element->attr
[$attr_name] = $def;
125 * Adds a custom element to your HTML definition
126 * @see HTMLPurifier_HTMLModule::addElement() for detailed
127 * parameter and return value descriptions.
129 public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
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);
139 * Adds a blank element to your HTML definition, for overriding
141 * @param string $element_name
142 * @return HTMLPurifier_ElementDef
143 * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
144 * parameter and return value descriptions.
146 public function addBlankElement($element_name)
148 $module = $this->getAnonymousModule();
149 $element = $module->addBlankElement($element_name);
154 * Retrieves a reference to the anonymous module, so you can
155 * bust out advanced features without having to make your own
157 * @return HTMLPurifier_HTMLModule
159 public function getAnonymousModule()
161 if (!$this->_anonModule
) {
162 $this->_anonModule
= new HTMLPurifier_HTMLModule();
163 $this->_anonModule
->name
= 'Anonymous';
165 return $this->_anonModule
;
168 private $_anonModule = null;
170 // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
175 public $type = 'HTML';
178 * @type HTMLPurifier_HTMLModuleManager
183 * Performs low-cost, preliminary initialization.
185 public function __construct()
187 $this->manager
= new HTMLPurifier_HTMLModuleManager();
191 * @param HTMLPurifier_Config $config
193 protected function doSetup($config)
195 $this->processModules($config);
196 $this->setupConfigStuff($config);
197 unset($this->manager
);
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
);
207 * Extract out the information from the manager
208 * @param HTMLPurifier_Config $config
210 protected function processModules($config)
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
);
220 $this->manager
->setup($config);
221 $this->doctype
= $this->manager
->doctype
;
223 foreach ($this->manager
->modules
as $module) {
224 foreach ($module->info_tag_transform
as $k => $v) {
226 unset($this->info_tag_transform
[$k]);
228 $this->info_tag_transform
[$k] = $v;
231 foreach ($module->info_attr_transform_pre
as $k => $v) {
233 unset($this->info_attr_transform_pre
[$k]);
235 $this->info_attr_transform_pre
[$k] = $v;
238 foreach ($module->info_attr_transform_post
as $k => $v) {
240 unset($this->info_attr_transform_post
[$k]);
242 $this->info_attr_transform_post
[$k] = $v;
245 foreach ($module->info_injector
as $k => $v) {
247 unset($this->info_injector
[$k]);
249 $this->info_injector
[$k] = $v;
253 $this->info
= $this->manager
->getElements();
254 $this->info_content_sets
= $this->manager
->contentSets
->lookup
;
258 * Sets up stuff based on config. We need a better way of doing this.
259 * @param HTMLPurifier_Config $config
261 protected function setupConfigStuff($config)
263 $block_wrapper = $config->get('HTML.BlockWrapper');
264 if (isset($this->info_content_sets
['Block'][$block_wrapper])) {
265 $this->info_block_wrapper
= $block_wrapper;
268 'Cannot use non-block element as block wrapper',
273 $parent = $config->get('HTML.Parent');
274 $def = $this->manager
->getElement($parent, true);
276 $this->info_parent
= $parent;
277 $this->info_parent_def
= $def;
280 'Cannot use unrecognized element as parent',
283 $this->info_parent_def
= $this->manager
->getElement($this->info_parent
, true);
286 // support template text
287 $support = "(for information on implementing this, see the support forums) ";
289 // setup allowed elements -----------------------------------------
291 $allowed_elements = $config->get('HTML.AllowedElements');
292 $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
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);
301 if (is_array($allowed_elements)) {
302 foreach ($this->info
as $name => $d) {
303 if (!isset($allowed_elements[$name])) {
304 unset($this->info
[$name]);
306 unset($allowed_elements[$name]);
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
);
315 // setup allowed attributes ---------------------------------------
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");
325 foreach ($keys as $key) {
326 if ($delete && isset($allowed_attributes[$key])) {
329 if (isset($allowed_attributes_mutable[$key])) {
330 unset($allowed_attributes_mutable[$key]);
334 unset($this->info_global_attr
[$attr]);
338 foreach ($this->info
as $tag => $info) {
339 foreach ($info->attr
as $attr => $x) {
340 $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
342 foreach ($keys as $key) {
343 if ($delete && isset($allowed_attributes[$key])) {
346 if (isset($allowed_attributes_mutable[$key])) {
347 unset($allowed_attributes_mutable[$key]);
351 if ($this->info
[$tag]->attr
[$attr]->required
) {
353 "Required attribute '$attr' in element '$tag' " .
354 "was not allowed, which means '$tag' will not be allowed either",
358 unset($this->info
[$tag]->attr
[$attr]);
363 foreach ($allowed_attributes_mutable as $elattr => $d) {
364 $bits = preg_split('/[.@]/', $elattr, 2);
368 if ($bits[0] !== '*') {
369 $element = htmlspecialchars($bits[0]);
370 $attribute = htmlspecialchars($bits[1]);
371 if (!isset($this->info
[$element])) {
373 "Cannot allow attribute '$attribute' if element " .
374 "'$element' is not allowed/supported $support"
378 "Attribute '$attribute' in element '$element' not supported $support",
384 // otherwise fall through
386 $attribute = htmlspecialchars($bits[0]);
388 "Global attribute '$attribute' is not ".
389 "supported in any elements $support",
397 // setup forbidden elements ---------------------------------------
399 $forbidden_elements = $config->get('HTML.ForbiddenElements');
400 $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
402 foreach ($this->info
as $tag => $info) {
403 if (isset($forbidden_elements[$tag])) {
404 unset($this->info
[$tag]);
407 foreach ($info->attr
as $attr => $x) {
408 if (isset($forbidden_attributes["$tag@$attr"]) ||
409 isset($forbidden_attributes["*@$attr"]) ||
410 isset($forbidden_attributes[$attr])
412 unset($this->info
[$tag]->attr
[$attr]);
414 } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
415 // $tag.$attr are not user supplied, so no worries!
417 "Error with $tag.$attr: tag.attr syntax not supported for " .
418 "HTML.ForbiddenAttributes; use tag@attr instead",
424 foreach ($forbidden_attributes as $key => $v) {
425 if (strlen($key) < 2) {
428 if ($key[0] != '*') {
431 if ($key[1] == '.') {
433 "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
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]);
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
456 * @todo Give this its own class, probably static interface
458 public function parseTinyMCEAllowedList($list)
460 $list = str_replace(array(' ', "\t"), '', $list);
463 $attributes = array();
465 $chunks = preg_split('/(,|[\n\r]+)/', $list);
466 foreach ($chunks as $chunk) {
470 // remove TinyMCE element control characters
471 if (!strpos($chunk, '[')) {
475 list($element, $attr) = explode('[', $chunk);
477 if ($element !== '*') {
478 $elements[$element] = true;
483 $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
484 $attr = explode('|', $attr);
485 foreach ($attr as $key) {
486 $attributes["$element.$key"] = true;
489 return array($elements, $attributes);
493 // vim: et sw=4 sts=4