Source for file HTMLDefinition.php

Documentation is available at 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. {
  27.     
  28.     // FULLY-PUBLIC VARIABLES ---------------------------------------------
  29.     
  30.     /**
  31.      * Associative array of element names to HTMLPurifier_ElementDef
  32.      */
  33.     public $info = array();
  34.     
  35.     /**
  36.      * Associative array of global attribute name to attribute definition.
  37.      */
  38.     public $info_global_attr = array();
  39.     
  40.     /**
  41.      * String name of parent element HTML will be going into.
  42.      */
  43.     public $info_parent = 'div';
  44.     
  45.     /**
  46.      * Definition for parent element, allows parent element to be a
  47.      * tag that's not allowed inside the HTML fragment.
  48.      */
  49.     public $info_parent_def;
  50.     
  51.     /**
  52.      * String name of element used to wrap inline elements in block context
  53.      * @note This is rarely used except for BLOCKQUOTEs in strict mode
  54.      */
  55.     public $info_block_wrapper = 'p';
  56.     
  57.     /**
  58.      * Associative array of deprecated tag name to HTMLPurifier_TagTransform
  59.      */
  60.     public $info_tag_transform = array();
  61.     
  62.     /**
  63.      * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
  64.      */
  65.     public $info_attr_transform_pre = array();
  66.     
  67.     /**
  68.      * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
  69.      */
  70.     public $info_attr_transform_post = array();
  71.     
  72.     /**
  73.      * Nested lookup array of content set name (Block, Inline) to
  74.      * element name to whether or not it belongs in that content set.
  75.      */
  76.     public $info_content_sets = array();
  77.     
  78.     /**
  79.      * Indexed list of HTMLPurifier_Injector to be used.
  80.      */
  81.     public $info_injector = array();
  82.     
  83.     /**
  84.      * Doctype object
  85.      */
  86.     public $doctype;
  87.     
  88.     
  89.     
  90.     // RAW CUSTOMIZATION STUFF --------------------------------------------
  91.     
  92.     /**
  93.      * Adds a custom attribute to a pre-existing element
  94.      * @note This is strictly convenience, and does not have a corresponding
  95.      *        method in HTMLPurifier_HTMLModule
  96.      * @param $element_name String element name to add attribute to
  97.      * @param $attr_name String name of attribute
  98.      * @param $def Attribute definition, can be string or object, see
  99.      *              HTMLPurifier_AttrTypes for details
  100.      */
  101.     public function addAttribute($element_name$attr_name$def{
  102.         $module $this->getAnonymousModule();
  103.         if (!isset($module->info[$element_name])) {
  104.             $element $module->addBlankElement($element_name);
  105.         else {
  106.             $element $module->info[$element_name];
  107.         }
  108.         $element->attr[$attr_name$def;
  109.     }
  110.     
  111.     /**
  112.      * Adds a custom element to your HTML definition
  113.      * @note See HTMLPurifier_HTMLModule::addElement for detailed
  114.      *        parameter and return value descriptions.
  115.      */
  116.     public function addElement($element_name$type$contents$attr_collections$attributes{
  117.         $module $this->getAnonymousModule();
  118.         // assume that if the user is calling this, the element
  119.         // is safe. This may not be a good idea
  120.         $element $module->addElement($element_name$type$contents$attr_collections$attributes);
  121.         return $element;
  122.     }
  123.     
  124.     /**
  125.      * Adds a blank element to your HTML definition, for overriding
  126.      * existing behavior
  127.      * @note See HTMLPurifier_HTMLModule::addBlankElement for detailed
  128.      *        parameter and return value descriptions.
  129.      */
  130.     public function addBlankElement($element_name{
  131.         $module  $this->getAnonymousModule();
  132.         $element $module->addBlankElement($element_name);
  133.         return $element;
  134.     }
  135.     
  136.     /**
  137.      * Retrieves a reference to the anonymous module, so you can
  138.      * bust out advanced features without having to make your own
  139.      * module.
  140.      */
  141.     public function getAnonymousModule({
  142.         if (!$this->_anonModule{
  143.             $this->_anonModule new HTMLPurifier_HTMLModule();
  144.             $this->_anonModule->name 'Anonymous';
  145.         }
  146.         return $this->_anonModule;
  147.     }
  148.     
  149.     private $_anonModule;
  150.     
  151.     
  152.     // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
  153.     
  154.     public $type = 'HTML';
  155.     public $manager/**< Instance of HTMLPurifier_HTMLModuleManager */
  156.     
  157.     /**
  158.      * Performs low-cost, preliminary initialization.
  159.      */
  160.     public function __construct({
  161.         $this->manager = new HTMLPurifier_HTMLModuleManager();
  162.     }
  163.     
  164.     protected function doSetup($config{
  165.         $this->processModules($config);
  166.         $this->setupConfigStuff($config);
  167.         unset($this->manager);
  168.         
  169.         // cleanup some of the element definitions
  170.         foreach ($this->info as $k => $v{
  171.             unset($this->info[$k]->content_model);
  172.             unset($this->info[$k]->content_model_type);
  173.         }
  174.     }
  175.     
  176.     /**
  177.      * Extract out the information from the manager
  178.      */
  179.     protected function processModules($config{
  180.         
  181.         if ($this->_anonModule{
  182.             // for user specific changes
  183.             // this is late-loaded so we don't have to deal with PHP4
  184.             // reference wonky-ness
  185.             $this->manager->addModule($this->_anonModule);
  186.             unset($this->_anonModule);
  187.         }
  188.         
  189.         $this->manager->setup($config);
  190.         $this->doctype = $this->manager->doctype;
  191.         
  192.         foreach ($this->manager->modules as $module{
  193.             foreach($module->info_tag_transform as $k => $v{
  194.                 if ($v === falseunset($this->info_tag_transform[$k]);
  195.                 else $this->info_tag_transform[$k$v;
  196.             }
  197.             foreach($module->info_attr_transform_pre as $k => $v{
  198.                 if ($v === falseunset($this->info_attr_transform_pre[$k]);
  199.                 else $this->info_attr_transform_pre[$k$v;
  200.             }
  201.             foreach($module->info_attr_transform_post as $k => $v{
  202.                 if ($v === falseunset($this->info_attr_transform_post[$k]);
  203.                 else $this->info_attr_transform_post[$k$v;
  204.             }
  205.             foreach ($module->info_injector as $k => $v{
  206.                 if ($v === falseunset($this->info_injector[$k]);
  207.                 else $this->info_injector[$k$v;
  208.             }
  209.         }
  210.         
  211.         $this->info = $this->manager->getElements();
  212.         $this->info_content_sets = $this->manager->contentSets->lookup;
  213.         
  214.     }
  215.     
  216.     /**
  217.      * Sets up stuff based on config. We need a better way of doing this.
  218.      */
  219.     protected function setupConfigStuff($config{
  220.         
  221.         $block_wrapper $config->get('HTML''BlockWrapper');
  222.         if (isset($this->info_content_sets['Block'][$block_wrapper])) {
  223.             $this->info_block_wrapper = $block_wrapper;
  224.         else {
  225.             trigger_error('Cannot use non-block element as block wrapper',
  226.                 E_USER_ERROR);
  227.         }
  228.         
  229.         $parent $config->get('HTML''Parent');
  230.         $def $this->manager->getElement($parenttrue);
  231.         if ($def{
  232.             $this->info_parent = $parent;
  233.             $this->info_parent_def = $def;
  234.         else {
  235.             trigger_error('Cannot use unrecognized element as parent',
  236.                 E_USER_ERROR);
  237.             $this->info_parent_def = $this->manager->getElement($this->info_parenttrue);
  238.         }
  239.         
  240.         // support template text
  241.         $support "(for information on implementing this, see the ".
  242.                    "support forums) ";
  243.         
  244.         // setup allowed elements -----------------------------------------
  245.         
  246.         $allowed_elements $config->get('HTML''AllowedElements');
  247.         $allowed_attributes $config->get('HTML''AllowedAttributes')// retrieve early
  248.         
  249.         if (!is_array($allowed_elements&& !is_array($allowed_attributes)) {
  250.             $allowed $config->get('HTML''Allowed');
  251.             if (is_string($allowed)) {
  252.                 list($allowed_elements$allowed_attributes$this->parseTinyMCEAllowedList($allowed);
  253.             }
  254.         }
  255.         
  256.         if (is_array($allowed_elements)) {
  257.             foreach ($this->info as $name => $d{
  258.                 if(!isset($allowed_elements[$name])) unset($this->info[$name]);
  259.                 unset($allowed_elements[$name]);
  260.             }
  261.             // emit errors
  262.             foreach ($allowed_elements as $element => $d{
  263.                 $element htmlspecialchars($element)// PHP doesn't escape errors, be careful!
  264.                 trigger_error("Element '$element' is not supported $support"E_USER_WARNING);
  265.             }
  266.         }
  267.         
  268.         // setup allowed attributes ---------------------------------------
  269.         
  270.         $allowed_attributes_mutable $allowed_attributes// by copy!
  271.         if (is_array($allowed_attributes)) {
  272.             
  273.             // This actually doesn't do anything, since we went away from
  274.             // global attributes. It's possible that userland code uses
  275.             // it, but HTMLModuleManager doesn't!
  276.             foreach ($this->info_global_attr as $attr => $x{
  277.                 $keys array($attr"*@$attr""*.$attr");
  278.                 $delete true;
  279.                 foreach ($keys as $key{
  280.                     if ($delete && isset($allowed_attributes[$key])) {
  281.                         $delete false;
  282.                     }
  283.                     if (isset($allowed_attributes_mutable[$key])) {
  284.                         unset($allowed_attributes_mutable[$key]);
  285.                     }
  286.                 }
  287.                 if ($deleteunset($this->info_global_attr[$attr]);
  288.             }
  289.             
  290.             foreach ($this->info as $tag => $info{
  291.                 foreach ($info->attr as $attr => $x{
  292.                     $keys array("$tag@$attr"$attr"*@$attr""$tag.$attr""*.$attr");
  293.                     $delete true;
  294.                     foreach ($keys as $key{
  295.                         if ($delete && isset($allowed_attributes[$key])) {
  296.                             $delete false;
  297.                         }
  298.                         if (isset($allowed_attributes_mutable[$key])) {
  299.                             unset($allowed_attributes_mutable[$key]);
  300.                         }
  301.                     }
  302.                     if ($deleteunset($this->info[$tag]->attr[$attr]);
  303.                 }
  304.             }
  305.             // emit errors
  306.             foreach ($allowed_attributes_mutable as $elattr => $d{
  307.                 $bits preg_split('/[.@]/'$elattr2);
  308.                 $c count($bits);
  309.                 switch ($c{
  310.                     case 2:
  311.                         if ($bits[0!== '*'{
  312.                             $element htmlspecialchars($bits[0]);
  313.                             $attribute htmlspecialchars($bits[1]);
  314.                             if (!isset($this->info[$element])) {
  315.                                 trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support");
  316.                             else {
  317.                                 trigger_error("Attribute '$attribute' in element '$element' not supported $support",
  318.                                     E_USER_WARNING);
  319.                             }
  320.                             break;
  321.                         }
  322.                         // otherwise fall through
  323.                     case 1:
  324.                         $attribute htmlspecialchars($bits[0]);
  325.                         trigger_error("Global attribute '$attribute' is not ".
  326.                             "supported in any elements $support",
  327.                             E_USER_WARNING);
  328.                         break;
  329.                 }
  330.             }
  331.             
  332.         }
  333.         
  334.         // setup forbidden elements ---------------------------------------
  335.         
  336.         $forbidden_elements   $config->get('HTML''ForbiddenElements');
  337.         $forbidden_attributes $config->get('HTML''ForbiddenAttributes');
  338.         
  339.         foreach ($this->info as $tag => $info{
  340.             if (isset($forbidden_elements[$tag])) {
  341.                 unset($this->info[$tag]);
  342.                 continue;
  343.             }
  344.             foreach ($info->attr as $attr => $x{
  345.                 if (
  346.                     isset($forbidden_attributes["$tag@$attr"]||
  347.                     isset($forbidden_attributes["*@$attr"]||
  348.                     isset($forbidden_attributes[$attr])
  349.                 {
  350.                     unset($this->info[$tag]->attr[$attr]);
  351.                     continue;
  352.                 // this segment might get removed eventually
  353.                 elseif (isset($forbidden_attributes["$tag.$attr"])) {
  354.                     // $tag.$attr are not user supplied, so no worries!
  355.                     trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead"E_USER_WARNING);
  356.                 }
  357.             }
  358.         }
  359.         foreach ($forbidden_attributes as $key => $v{
  360.             if (strlen($key2continue;
  361.             if ($key[0!= '*'continue;
  362.             if ($key[1== '.'{
  363.                 trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead"E_USER_WARNING);
  364.             }
  365.         }
  366.         
  367.         // setup injectors -----------------------------------------------------
  368.         foreach ($this->info_injector as $i => $injector{
  369.             if ($injector->checkNeeded($config!== false{
  370.                 // remove injector that does not have it's required
  371.                 // elements/attributes present, and is thus not needed.
  372.                 unset($this->info_injector[$i]);
  373.             }
  374.         }
  375.     }
  376.     
  377.     /**
  378.      * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
  379.      * separate lists for processing. Format is element[attr1|attr2],element2...
  380.      * @warning Although it's largely drawn from TinyMCE's implementation,
  381.      *       it is different, and you'll probably have to modify your lists
  382.      * @param $list String list to parse
  383.      * @param array($allowed_elements, $allowed_attributes) 
  384.      * @todo Give this its own class, probably static interface
  385.      */
  386.     public function parseTinyMCEAllowedList($list{
  387.         
  388.         $list str_replace(array(' '"\t")''$list);
  389.         
  390.         $elements array();
  391.         $attributes array();
  392.         
  393.         $chunks preg_split('/(,|[\n\r]+)/'$list);
  394.         foreach ($chunks as $chunk{
  395.             if (empty($chunk)) continue;
  396.             // remove TinyMCE element control characters
  397.             if (!strpos($chunk'[')) {
  398.                 $element $chunk;
  399.                 $attr false;
  400.             else {
  401.                 list($element$attrexplode('['$chunk);
  402.             }
  403.             if ($element !== '*'$elements[$elementtrue;
  404.             if (!$attrcontinue;
  405.             $attr substr($attr0strlen($attr1)// remove trailing ]
  406.             $attr explode('|'$attr);
  407.             foreach ($attr as $key{
  408.                 $attributes["$element.$key"true;
  409.             }
  410.         }
  411.         
  412.         return array($elements$attributes);
  413.         
  414.     }
  415.     
  416.     
  417. }

Documentation generated on Thu, 19 Jun 2008 18:49:29 -0400 by phpDocumentor 1.4.2