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