HTMLPurifier 4.4.0
/home/ezyang/Dev/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php
Go to the documentation of this file.
00001 <?php
00002 
00026 class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
00027 {
00028 
00029     // FULLY-PUBLIC VARIABLES ---------------------------------------------
00030 
00034     public $info = array();
00035 
00039     public $info_global_attr = array();
00040 
00044     public $info_parent = 'div';
00045 
00050     public $info_parent_def;
00051 
00056     public $info_block_wrapper = 'p';
00057 
00061     public $info_tag_transform = array();
00062 
00066     public $info_attr_transform_pre = array();
00067 
00071     public $info_attr_transform_post = array();
00072 
00077     public $info_content_sets = array();
00078 
00082     public $info_injector = array();
00083 
00087     public $doctype;
00088 
00089 
00090 
00091     // RAW CUSTOMIZATION STUFF --------------------------------------------
00092 
00102     public function addAttribute($element_name, $attr_name, $def) {
00103         $module = $this->getAnonymousModule();
00104         if (!isset($module->info[$element_name])) {
00105             $element = $module->addBlankElement($element_name);
00106         } else {
00107             $element = $module->info[$element_name];
00108         }
00109         $element->attr[$attr_name] = $def;
00110     }
00111 
00117     public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) {
00118         $module = $this->getAnonymousModule();
00119         // assume that if the user is calling this, the element
00120         // is safe. This may not be a good idea
00121         $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
00122         return $element;
00123     }
00124 
00131     public function addBlankElement($element_name) {
00132         $module  = $this->getAnonymousModule();
00133         $element = $module->addBlankElement($element_name);
00134         return $element;
00135     }
00136 
00142     public function getAnonymousModule() {
00143         if (!$this->_anonModule) {
00144             $this->_anonModule = new HTMLPurifier_HTMLModule();
00145             $this->_anonModule->name = 'Anonymous';
00146         }
00147         return $this->_anonModule;
00148     }
00149 
00150     private $_anonModule = null;
00151 
00152 
00153     // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
00154 
00155     public $type = 'HTML';
00156     public $manager; 
00161     public function __construct() {
00162         $this->manager = new HTMLPurifier_HTMLModuleManager();
00163     }
00164 
00165     protected function doSetup($config) {
00166         $this->processModules($config);
00167         $this->setupConfigStuff($config);
00168         unset($this->manager);
00169 
00170         // cleanup some of the element definitions
00171         foreach ($this->info as $k => $v) {
00172             unset($this->info[$k]->content_model);
00173             unset($this->info[$k]->content_model_type);
00174         }
00175     }
00176 
00180     protected function processModules($config) {
00181 
00182         if ($this->_anonModule) {
00183             // for user specific changes
00184             // this is late-loaded so we don't have to deal with PHP4
00185             // reference wonky-ness
00186             $this->manager->addModule($this->_anonModule);
00187             unset($this->_anonModule);
00188         }
00189 
00190         $this->manager->setup($config);
00191         $this->doctype = $this->manager->doctype;
00192 
00193         foreach ($this->manager->modules as $module) {
00194             foreach($module->info_tag_transform as $k => $v) {
00195                 if ($v === false) unset($this->info_tag_transform[$k]);
00196                 else $this->info_tag_transform[$k] = $v;
00197             }
00198             foreach($module->info_attr_transform_pre as $k => $v) {
00199                 if ($v === false) unset($this->info_attr_transform_pre[$k]);
00200                 else $this->info_attr_transform_pre[$k] = $v;
00201             }
00202             foreach($module->info_attr_transform_post as $k => $v) {
00203                 if ($v === false) unset($this->info_attr_transform_post[$k]);
00204                 else $this->info_attr_transform_post[$k] = $v;
00205             }
00206             foreach ($module->info_injector as $k => $v) {
00207                 if ($v === false) unset($this->info_injector[$k]);
00208                 else $this->info_injector[$k] = $v;
00209             }
00210         }
00211 
00212         $this->info = $this->manager->getElements();
00213         $this->info_content_sets = $this->manager->contentSets->lookup;
00214 
00215     }
00216 
00220     protected function setupConfigStuff($config) {
00221 
00222         $block_wrapper = $config->get('HTML.BlockWrapper');
00223         if (isset($this->info_content_sets['Block'][$block_wrapper])) {
00224             $this->info_block_wrapper = $block_wrapper;
00225         } else {
00226             trigger_error('Cannot use non-block element as block wrapper',
00227                 E_USER_ERROR);
00228         }
00229 
00230         $parent = $config->get('HTML.Parent');
00231         $def = $this->manager->getElement($parent, true);
00232         if ($def) {
00233             $this->info_parent = $parent;
00234             $this->info_parent_def = $def;
00235         } else {
00236             trigger_error('Cannot use unrecognized element as parent',
00237                 E_USER_ERROR);
00238             $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
00239         }
00240 
00241         // support template text
00242         $support = "(for information on implementing this, see the ".
00243                    "support forums) ";
00244 
00245         // setup allowed elements -----------------------------------------
00246 
00247         $allowed_elements = $config->get('HTML.AllowedElements');
00248         $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
00249 
00250         if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
00251             $allowed = $config->get('HTML.Allowed');
00252             if (is_string($allowed)) {
00253                 list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
00254             }
00255         }
00256 
00257         if (is_array($allowed_elements)) {
00258             foreach ($this->info as $name => $d) {
00259                 if(!isset($allowed_elements[$name])) unset($this->info[$name]);
00260                 unset($allowed_elements[$name]);
00261             }
00262             // emit errors
00263             foreach ($allowed_elements as $element => $d) {
00264                 $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
00265                 trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
00266             }
00267         }
00268 
00269         // setup allowed attributes ---------------------------------------
00270 
00271         $allowed_attributes_mutable = $allowed_attributes; // by copy!
00272         if (is_array($allowed_attributes)) {
00273 
00274             // This actually doesn't do anything, since we went away from
00275             // global attributes. It's possible that userland code uses
00276             // it, but HTMLModuleManager doesn't!
00277             foreach ($this->info_global_attr as $attr => $x) {
00278                 $keys = array($attr, "*@$attr", "*.$attr");
00279                 $delete = true;
00280                 foreach ($keys as $key) {
00281                     if ($delete && isset($allowed_attributes[$key])) {
00282                         $delete = false;
00283                     }
00284                     if (isset($allowed_attributes_mutable[$key])) {
00285                         unset($allowed_attributes_mutable[$key]);
00286                     }
00287                 }
00288                 if ($delete) unset($this->info_global_attr[$attr]);
00289             }
00290 
00291             foreach ($this->info as $tag => $info) {
00292                 foreach ($info->attr as $attr => $x) {
00293                     $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
00294                     $delete = true;
00295                     foreach ($keys as $key) {
00296                         if ($delete && isset($allowed_attributes[$key])) {
00297                             $delete = false;
00298                         }
00299                         if (isset($allowed_attributes_mutable[$key])) {
00300                             unset($allowed_attributes_mutable[$key]);
00301                         }
00302                     }
00303                     if ($delete) {
00304                         if ($this->info[$tag]->attr[$attr]->required) {
00305                             trigger_error("Required attribute '$attr' in element '$tag' was not allowed, which means '$tag' will not be allowed either", E_USER_WARNING);
00306                         }
00307                         unset($this->info[$tag]->attr[$attr]);
00308                     }
00309                 }
00310             }
00311             // emit errors
00312             foreach ($allowed_attributes_mutable as $elattr => $d) {
00313                 $bits = preg_split('/[.@]/', $elattr, 2);
00314                 $c = count($bits);
00315                 switch ($c) {
00316                     case 2:
00317                         if ($bits[0] !== '*') {
00318                             $element = htmlspecialchars($bits[0]);
00319                             $attribute = htmlspecialchars($bits[1]);
00320                             if (!isset($this->info[$element])) {
00321                                 trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support");
00322                             } else {
00323                                 trigger_error("Attribute '$attribute' in element '$element' not supported $support",
00324                                     E_USER_WARNING);
00325                             }
00326                             break;
00327                         }
00328                         // otherwise fall through
00329                     case 1:
00330                         $attribute = htmlspecialchars($bits[0]);
00331                         trigger_error("Global attribute '$attribute' is not ".
00332                             "supported in any elements $support",
00333                             E_USER_WARNING);
00334                         break;
00335                 }
00336             }
00337 
00338         }
00339 
00340         // setup forbidden elements ---------------------------------------
00341 
00342         $forbidden_elements   = $config->get('HTML.ForbiddenElements');
00343         $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
00344 
00345         foreach ($this->info as $tag => $info) {
00346             if (isset($forbidden_elements[$tag])) {
00347                 unset($this->info[$tag]);
00348                 continue;
00349             }
00350             foreach ($info->attr as $attr => $x) {
00351                 if (
00352                     isset($forbidden_attributes["$tag@$attr"]) ||
00353                     isset($forbidden_attributes["*@$attr"]) ||
00354                     isset($forbidden_attributes[$attr])
00355                 ) {
00356                     unset($this->info[$tag]->attr[$attr]);
00357                     continue;
00358                 } // this segment might get removed eventually
00359                 elseif (isset($forbidden_attributes["$tag.$attr"])) {
00360                     // $tag.$attr are not user supplied, so no worries!
00361                     trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead", E_USER_WARNING);
00362                 }
00363             }
00364         }
00365         foreach ($forbidden_attributes as $key => $v) {
00366             if (strlen($key) < 2) continue;
00367             if ($key[0] != '*') continue;
00368             if ($key[1] == '.') {
00369                 trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", E_USER_WARNING);
00370             }
00371         }
00372 
00373         // setup injectors -----------------------------------------------------
00374         foreach ($this->info_injector as $i => $injector) {
00375             if ($injector->checkNeeded($config) !== false) {
00376                 // remove injector that does not have it's required
00377                 // elements/attributes present, and is thus not needed.
00378                 unset($this->info_injector[$i]);
00379             }
00380         }
00381     }
00382 
00392     public function parseTinyMCEAllowedList($list) {
00393 
00394         $list = str_replace(array(' ', "\t"), '', $list);
00395 
00396         $elements = array();
00397         $attributes = array();
00398 
00399         $chunks = preg_split('/(,|[\n\r]+)/', $list);
00400         foreach ($chunks as $chunk) {
00401             if (empty($chunk)) continue;
00402             // remove TinyMCE element control characters
00403             if (!strpos($chunk, '[')) {
00404                 $element = $chunk;
00405                 $attr = false;
00406             } else {
00407                 list($element, $attr) = explode('[', $chunk);
00408             }
00409             if ($element !== '*') $elements[$element] = true;
00410             if (!$attr) continue;
00411             $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
00412             $attr = explode('|', $attr);
00413             foreach ($attr as $key) {
00414                 $attributes["$element.$key"] = true;
00415             }
00416         }
00417 
00418         return array($elements, $attributes);
00419 
00420     }
00421 
00422 
00423 }
00424 
00425 // vim: et sw=4 sts=4