getAnonymousModule(); if (!isset($module->info[$element_name])) { $element = $module->addBlankElement($element_name); } else { $element = $module->info[$element_name]; } $element->attr[$attr_name] = $def; } /** * Adds a custom element to your HTML definition * @note See HTMLPurifier_HTMLModule::addElement for detailed * parameter and return value descriptions. */ public function addElement($element_name, $type, $contents, $attr_collections, $attributes) { $module = $this->getAnonymousModule(); // assume that if the user is calling this, the element // is safe. This may not be a good idea $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes); return $element; } /** * Adds a blank element to your HTML definition, for overriding * existing behavior * @note See HTMLPurifier_HTMLModule::addBlankElement for detailed * parameter and return value descriptions. */ public function addBlankElement($element_name) { $module = $this->getAnonymousModule(); $element = $module->addBlankElement($element_name); return $element; } /** * Retrieves a reference to the anonymous module, so you can * bust out advanced features without having to make your own * module. */ public function getAnonymousModule() { if (!$this->_anonModule) { $this->_anonModule = new HTMLPurifier_HTMLModule(); $this->_anonModule->name = 'Anonymous'; } return $this->_anonModule; } private $_anonModule; // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- public $type = 'HTML'; public $manager; /**< Instance of HTMLPurifier_HTMLModuleManager */ /** * Performs low-cost, preliminary initialization. */ public function __construct() { $this->manager = new HTMLPurifier_HTMLModuleManager(); } protected function doSetup($config) { $this->processModules($config); $this->setupConfigStuff($config); unset($this->manager); // cleanup some of the element definitions foreach ($this->info as $k => $v) { unset($this->info[$k]->content_model); unset($this->info[$k]->content_model_type); } } /** * Extract out the information from the manager */ protected function processModules($config) { if ($this->_anonModule) { // for user specific changes // this is late-loaded so we don't have to deal with PHP4 // reference wonky-ness $this->manager->addModule($this->_anonModule); unset($this->_anonModule); } $this->manager->setup($config); $this->doctype = $this->manager->doctype; foreach ($this->manager->modules as $module) { foreach($module->info_tag_transform as $k => $v) { if ($v === false) unset($this->info_tag_transform[$k]); else $this->info_tag_transform[$k] = $v; } foreach($module->info_attr_transform_pre as $k => $v) { if ($v === false) unset($this->info_attr_transform_pre[$k]); else $this->info_attr_transform_pre[$k] = $v; } foreach($module->info_attr_transform_post as $k => $v) { if ($v === false) unset($this->info_attr_transform_post[$k]); else $this->info_attr_transform_post[$k] = $v; } } $this->info = $this->manager->getElements(); $this->info_content_sets = $this->manager->contentSets->lookup; } /** * Sets up stuff based on config. We need a better way of doing this. */ protected function setupConfigStuff($config) { $block_wrapper = $config->get('HTML', 'BlockWrapper'); if (isset($this->info_content_sets['Block'][$block_wrapper])) { $this->info_block_wrapper = $block_wrapper; } else { trigger_error('Cannot use non-block element as block wrapper', E_USER_ERROR); } $parent = $config->get('HTML', 'Parent'); $def = $this->manager->getElement($parent, true); if ($def) { $this->info_parent = $parent; $this->info_parent_def = $def; } else { trigger_error('Cannot use unrecognized element as parent', E_USER_ERROR); $this->info_parent_def = $this->manager->getElement($this->info_parent, true); } // support template text $support = "(for information on implementing this, see the ". "support forums) "; // setup allowed elements ----------------------------------------- $allowed_elements = $config->get('HTML', 'AllowedElements'); $allowed_attributes = $config->get('HTML', 'AllowedAttributes'); // retrieve early if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { $allowed = $config->get('HTML', 'Allowed'); if (is_string($allowed)) { list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed); } } if (is_array($allowed_elements)) { foreach ($this->info as $name => $d) { if(!isset($allowed_elements[$name])) unset($this->info[$name]); unset($allowed_elements[$name]); } // emit errors foreach ($allowed_elements as $element => $d) { $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! trigger_error("Element '$element' is not supported $support", E_USER_WARNING); } } // setup allowed attributes --------------------------------------- $allowed_attributes_mutable = $allowed_attributes; // by copy! if (is_array($allowed_attributes)) { // This actually doesn't do anything, since we went away from // global attributes. It's possible that userland code uses // it, but HTMLModuleManager doesn't! foreach ($this->info_global_attr as $attr => $x) { $keys = array($attr, "*@$attr", "*.$attr"); $delete = true; foreach ($keys as $key) { if ($delete && isset($allowed_attributes[$key])) { $delete = false; } if (isset($allowed_attributes_mutable[$key])) { unset($allowed_attributes_mutable[$key]); } } if ($delete) unset($this->info_global_attr[$attr]); } foreach ($this->info as $tag => $info) { foreach ($info->attr as $attr => $x) { $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); $delete = true; foreach ($keys as $key) { if ($delete && isset($allowed_attributes[$key])) { $delete = false; } if (isset($allowed_attributes_mutable[$key])) { unset($allowed_attributes_mutable[$key]); } } if ($delete) unset($this->info[$tag]->attr[$attr]); } } // emit errors foreach ($allowed_attributes_mutable as $elattr => $d) { $bits = preg_split('/[.@]/', $elattr, 2); $c = count($bits); switch ($c) { case 2: if ($bits[0] !== '*') { $element = htmlspecialchars($bits[0]); $attribute = htmlspecialchars($bits[1]); if (!isset($this->info[$element])) { trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support"); } else { trigger_error("Attribute '$attribute' in element '$element' not supported $support", E_USER_WARNING); } break; } // otherwise fall through case 1: $attribute = htmlspecialchars($bits[0]); trigger_error("Global attribute '$attribute' is not ". "supported in any elements $support", E_USER_WARNING); break; } } } // setup forbidden elements --------------------------------------- $forbidden_elements = $config->get('HTML', 'ForbiddenElements'); $forbidden_attributes = $config->get('HTML', 'ForbiddenAttributes'); foreach ($this->info as $tag => $info) { if (isset($forbidden_elements[$tag])) { unset($this->info[$tag]); continue; } foreach ($info->attr as $attr => $x) { if ( isset($forbidden_attributes["$tag@$attr"]) || isset($forbidden_attributes["*@$attr"]) || isset($forbidden_attributes[$attr]) ) { unset($this->info[$tag]->attr[$attr]); continue; } // this segment might get removed eventually elseif (isset($forbidden_attributes["$tag.$attr"])) { // $tag.$attr are not user supplied, so no worries! trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead", E_USER_WARNING); } } } foreach ($forbidden_attributes as $key => $v) { if (strlen($key) < 2) continue; if ($key[0] != '*') continue; if ($key[1] == '.') { trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", E_USER_WARNING); } } } /** * Parses a TinyMCE-flavored Allowed Elements and Attributes list into * separate lists for processing. Format is element[attr1|attr2],element2... * @warning Although it's largely drawn from TinyMCE's implementation, * it is different, and you'll probably have to modify your lists * @param $list String list to parse * @param array($allowed_elements, $allowed_attributes) * @todo Give this its own class, probably static interface */ public function parseTinyMCEAllowedList($list) { $elements = array(); $attributes = array(); $chunks = preg_split('/(,|[\n\r]+)/', $list); foreach ($chunks as $chunk) { if (empty($chunk)) continue; // remove TinyMCE element control characters if (!strpos($chunk, '[')) { $element = $chunk; $attr = false; } else { list($element, $attr) = explode('[', $chunk); } if ($element !== '*') $elements[$element] = true; if (!$attr) continue; $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ] $attr = explode('|', $attr); foreach ($attr as $key) { $attributes["$element.$key"] = true; } } return array($elements, $attributes); } }