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
*
* @see HTMLPurifier_HTMLModule::addElement() for detailed
* parameter and return value descriptions.
*/
public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) {
$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
*
* @param string $element_name
* @return HTMLPurifier_ElementDef
* @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.
*
* @return HTMLPurifier_HTMLModule
*/
public function getAnonymousModule() {
if (! $this->_anonModule) {
$this->_anonModule = new HTMLPurifier_HTMLModule ();
$this->_anonModule->name = 'Anonymous';
}
return $this->_anonModule;
}
private $_anonModule = null;
// PUBLIC BUT INTERNAL VARIABLES --------------------------------------
/**
* @type string
*/
public $type = 'HTML';
/**
* @type HTMLPurifier_HTMLModuleManager
*/
public $manager;
/**
* Performs low-cost, preliminary initialization.
*/
public function __construct() {
$this->manager = new HTMLPurifier_HTMLModuleManager ();
}
/**
*
* @param HTMLPurifier_Config $config
*/
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
*
* @param HTMLPurifier_Config $config
*/
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;
}
}
foreach ( $module->info_injector as $k => $v ) {
if ($v === false) {
unset ( $this->info_injector [$k] );
} else {
$this->info_injector [$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.
*
* @param HTMLPurifier_Config $config
*/
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) {
if ($this->info [$tag]->attr [$attr]->required) {
trigger_error ( "Required attribute '$attr' in element '$tag' " . "was not allowed, which means '$tag' will not be allowed either", E_USER_WARNING );
}
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;
} elseif (isset ( $forbidden_attributes ["$tag.$attr"] )) { // this segment might get removed eventually
// $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 );
}
}
// setup injectors -----------------------------------------------------
foreach ( $this->info_injector as $i => $injector ) {
if ($injector->checkNeeded ( $config ) !== false) {
// remove injector that does not have it's required
// elements/attributes present, and is thus not needed.
unset ( $this->info_injector [$i] );
}
}
}
/**
* 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 array $list
* String list to parse
* @return array
* @todo Give this its own class, probably static interface
*/
public function parseTinyMCEAllowedList($list) {
$list = str_replace ( array (
' ',
"\t"
), '', $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
);
}
}
// vim: et sw=4 sts=4