ILIAS  Release_4_1_x_branch Revision 61804
 All Data Structures Namespaces Files Functions Variables Groups Pages
HTMLModuleManager.php
Go to the documentation of this file.
1 <?php
2 
4 {
5 
9  public $doctypes;
10 
14  public $doctype;
15 
19  public $attrTypes;
20 
25  public $modules = array();
26 
32  public $registeredModules = array();
33 
39  public $userModules = array();
40 
45  public $elementLookup = array();
46 
48  public $prefixes = array('HTMLPurifier_HTMLModule_');
49 
50  public $contentSets;
54  public $trusted = false;
55 
56  public function __construct() {
57 
58  // editable internal objects
59  $this->attrTypes = new HTMLPurifier_AttrTypes();
60  $this->doctypes = new HTMLPurifier_DoctypeRegistry();
61 
62  // setup basic modules
63  $common = array(
64  'CommonAttributes', 'Text', 'Hypertext', 'List',
65  'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
66  'StyleAttribute',
67  // Unsafe:
68  'Scripting', 'Object', 'Forms',
69  // Sorta legacy, but present in strict:
70  'Name',
71  );
72  $transitional = array('Legacy', 'Target');
73  $xml = array('XMLCommonAttributes');
74  $non_xml = array('NonXMLCommonAttributes');
75 
76  // setup basic doctypes
77  $this->doctypes->register(
78  'HTML 4.01 Transitional', false,
79  array_merge($common, $transitional, $non_xml),
80  array('Tidy_Transitional', 'Tidy_Proprietary'),
81  array(),
82  '-//W3C//DTD HTML 4.01 Transitional//EN',
83  'http://www.w3.org/TR/html4/loose.dtd'
84  );
85 
86  $this->doctypes->register(
87  'HTML 4.01 Strict', false,
88  array_merge($common, $non_xml),
89  array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
90  array(),
91  '-//W3C//DTD HTML 4.01//EN',
92  'http://www.w3.org/TR/html4/strict.dtd'
93  );
94 
95  $this->doctypes->register(
96  'XHTML 1.0 Transitional', true,
97  array_merge($common, $transitional, $xml, $non_xml),
98  array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
99  array(),
100  '-//W3C//DTD XHTML 1.0 Transitional//EN',
101  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
102  );
103 
104  $this->doctypes->register(
105  'XHTML 1.0 Strict', true,
106  array_merge($common, $xml, $non_xml),
107  array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
108  array(),
109  '-//W3C//DTD XHTML 1.0 Strict//EN',
110  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
111  );
112 
113  $this->doctypes->register(
114  'XHTML 1.1', true,
115  array_merge($common, $xml, array('Ruby')),
116  array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
117  array(),
118  '-//W3C//DTD XHTML 1.1//EN',
119  'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
120  );
121 
122  }
123 
145  public function registerModule($module, $overload = false) {
146  if (is_string($module)) {
147  // attempt to load the module
148  $original_module = $module;
149  $ok = false;
150  foreach ($this->prefixes as $prefix) {
151  $module = $prefix . $original_module;
152  if (class_exists($module)) {
153  $ok = true;
154  break;
155  }
156  }
157  if (!$ok) {
158  $module = $original_module;
159  if (!class_exists($module)) {
160  trigger_error($original_module . ' module does not exist',
161  E_USER_ERROR);
162  return;
163  }
164  }
165  $module = new $module();
166  }
167  if (empty($module->name)) {
168  trigger_error('Module instance of ' . get_class($module) . ' must have name');
169  return;
170  }
171  if (!$overload && isset($this->registeredModules[$module->name])) {
172  trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
173  }
174  $this->registeredModules[$module->name] = $module;
175  }
176 
181  public function addModule($module) {
182  $this->registerModule($module);
183  if (is_object($module)) $module = $module->name;
184  $this->userModules[] = $module;
185  }
186 
191  public function addPrefix($prefix) {
192  $this->prefixes[] = $prefix;
193  }
194 
200  public function setup($config) {
201 
202  $this->trusted = $config->get('HTML.Trusted');
203 
204  // generate
205  $this->doctype = $this->doctypes->make($config);
206  $modules = $this->doctype->modules;
207 
208  // take out the default modules that aren't allowed
209  $lookup = $config->get('HTML.AllowedModules');
210  $special_cases = $config->get('HTML.CoreModules');
211 
212  if (is_array($lookup)) {
213  foreach ($modules as $k => $m) {
214  if (isset($special_cases[$m])) continue;
215  if (!isset($lookup[$m])) unset($modules[$k]);
216  }
217  }
218 
219  // custom modules
220  if ($config->get('HTML.Proprietary')) {
221  $modules[] = 'Proprietary';
222  }
223  if ($config->get('HTML.SafeObject')) {
224  $modules[] = 'SafeObject';
225  }
226  if ($config->get('HTML.SafeEmbed')) {
227  $modules[] = 'SafeEmbed';
228  }
229  if ($config->get('HTML.Nofollow')) {
230  $modules[] = 'Nofollow';
231  }
232 
233  // merge in custom modules
234  $modules = array_merge($modules, $this->userModules);
235 
236  foreach ($modules as $module) {
237  $this->processModule($module);
238  $this->modules[$module]->setup($config);
239  }
240 
241  foreach ($this->doctype->tidyModules as $module) {
242  $this->processModule($module);
243  $this->modules[$module]->setup($config);
244  }
245 
246  // prepare any injectors
247  foreach ($this->modules as $module) {
248  $n = array();
249  foreach ($module->info_injector as $i => $injector) {
250  if (!is_object($injector)) {
251  $class = "HTMLPurifier_Injector_$injector";
252  $injector = new $class;
253  }
254  $n[$injector->name] = $injector;
255  }
256  $module->info_injector = $n;
257  }
258 
259  // setup lookup table based on all valid modules
260  foreach ($this->modules as $module) {
261  foreach ($module->info as $name => $def) {
262  if (!isset($this->elementLookup[$name])) {
263  $this->elementLookup[$name] = array();
264  }
265  $this->elementLookup[$name][] = $module->name;
266  }
267  }
268 
269  // note the different choice
270  $this->contentSets = new HTMLPurifier_ContentSets(
271  // content set assembly deals with all possible modules,
272  // not just ones deemed to be "safe"
273  $this->modules
274  );
275  $this->attrCollections = new HTMLPurifier_AttrCollections(
276  $this->attrTypes,
277  // there is no way to directly disable a global attribute,
278  // but using AllowedAttributes or simply not including
279  // the module in your custom doctype should be sufficient
280  $this->modules
281  );
282  }
283 
288  public function processModule($module) {
289  if (!isset($this->registeredModules[$module]) || is_object($module)) {
290  $this->registerModule($module);
291  }
292  $this->modules[$module] = $this->registeredModules[$module];
293  }
294 
299  public function getElements() {
300 
301  $elements = array();
302  foreach ($this->modules as $module) {
303  if (!$this->trusted && !$module->safe) continue;
304  foreach ($module->info as $name => $v) {
305  if (isset($elements[$name])) continue;
306  $elements[$name] = $this->getElement($name);
307  }
308  }
309 
310  // remove dud elements, this happens when an element that
311  // appeared to be safe actually wasn't
312  foreach ($elements as $n => $v) {
313  if ($v === false) unset($elements[$n]);
314  }
315 
316  return $elements;
317 
318  }
319 
330  public function getElement($name, $trusted = null) {
331 
332  if (!isset($this->elementLookup[$name])) {
333  return false;
334  }
335 
336  // setup global state variables
337  $def = false;
338  if ($trusted === null) $trusted = $this->trusted;
339 
340  // iterate through each module that has registered itself to this
341  // element
342  foreach($this->elementLookup[$name] as $module_name) {
343 
344  $module = $this->modules[$module_name];
345 
346  // refuse to create/merge from a module that is deemed unsafe--
347  // pretend the module doesn't exist--when trusted mode is not on.
348  if (!$trusted && !$module->safe) {
349  continue;
350  }
351 
352  // clone is used because, ideally speaking, the original
353  // definition should not be modified. Usually, this will
354  // make no difference, but for consistency's sake
355  $new_def = clone $module->info[$name];
356 
357  if (!$def && $new_def->standalone) {
358  $def = $new_def;
359  } elseif ($def) {
360  // This will occur even if $new_def is standalone. In practice,
361  // this will usually result in a full replacement.
362  $def->mergeIn($new_def);
363  } else {
364  // :TODO:
365  // non-standalone definitions that don't have a standalone
366  // to merge into could be deferred to the end
367  continue;
368  }
369 
370  // attribute value expansions
371  $this->attrCollections->performInclusions($def->attr);
372  $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
373 
374  // descendants_are_inline, for ChildDef_Chameleon
375  if (is_string($def->content_model) &&
376  strpos($def->content_model, 'Inline') !== false) {
377  if ($name != 'del' && $name != 'ins') {
378  // this is for you, ins/del
379  $def->descendants_are_inline = true;
380  }
381  }
382 
383  $this->contentSets->generateChildDef($def, $module);
384  }
385 
386  // This can occur if there is a blank definition, but no base to
387  // mix it in with
388  if (!$def) return false;
389 
390  // add information on required attributes
391  foreach ($def->attr as $attr_name => $attr_def) {
392  if ($attr_def->required) {
393  $def->required_attr[] = $attr_name;
394  }
395  }
396 
397  return $def;
398 
399  }
400 
401 }
402 
403 // vim: et sw=4 sts=4