ILIAS  release_4-3 Revision
 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', 'Iframe');
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  // Iframe is a real XHTML 1.1 module, despite being
116  // "transitional"!
117  array_merge($common, $xml, array('Ruby', 'Iframe')),
118  array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
119  array(),
120  '-//W3C//DTD XHTML 1.1//EN',
121  'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
122  );
123 
124  }
125 
147  public function registerModule($module, $overload = false) {
148  if (is_string($module)) {
149  // attempt to load the module
150  $original_module = $module;
151  $ok = false;
152  foreach ($this->prefixes as $prefix) {
153  $module = $prefix . $original_module;
154  if (class_exists($module)) {
155  $ok = true;
156  break;
157  }
158  }
159  if (!$ok) {
160  $module = $original_module;
161  if (!class_exists($module)) {
162  trigger_error($original_module . ' module does not exist',
163  E_USER_ERROR);
164  return;
165  }
166  }
167  $module = new $module();
168  }
169  if (empty($module->name)) {
170  trigger_error('Module instance of ' . get_class($module) . ' must have name');
171  return;
172  }
173  if (!$overload && isset($this->registeredModules[$module->name])) {
174  trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
175  }
176  $this->registeredModules[$module->name] = $module;
177  }
178 
183  public function addModule($module) {
184  $this->registerModule($module);
185  if (is_object($module)) $module = $module->name;
186  $this->userModules[] = $module;
187  }
188 
193  public function addPrefix($prefix) {
194  $this->prefixes[] = $prefix;
195  }
196 
202  public function setup($config) {
203 
204  $this->trusted = $config->get('HTML.Trusted');
205 
206  // generate
207  $this->doctype = $this->doctypes->make($config);
208  $modules = $this->doctype->modules;
209 
210  // take out the default modules that aren't allowed
211  $lookup = $config->get('HTML.AllowedModules');
212  $special_cases = $config->get('HTML.CoreModules');
213 
214  if (is_array($lookup)) {
215  foreach ($modules as $k => $m) {
216  if (isset($special_cases[$m])) continue;
217  if (!isset($lookup[$m])) unset($modules[$k]);
218  }
219  }
220 
221  // custom modules
222  if ($config->get('HTML.Proprietary')) {
223  $modules[] = 'Proprietary';
224  }
225  if ($config->get('HTML.SafeObject')) {
226  $modules[] = 'SafeObject';
227  }
228  if ($config->get('HTML.SafeEmbed')) {
229  $modules[] = 'SafeEmbed';
230  }
231  if ($config->get('HTML.Nofollow')) {
232  $modules[] = 'Nofollow';
233  }
234  if ($config->get('HTML.TargetBlank')) {
235  $modules[] = 'TargetBlank';
236  }
237 
238  // merge in custom modules
239  $modules = array_merge($modules, $this->userModules);
240 
241  foreach ($modules as $module) {
242  $this->processModule($module);
243  $this->modules[$module]->setup($config);
244  }
245 
246  foreach ($this->doctype->tidyModules as $module) {
247  $this->processModule($module);
248  $this->modules[$module]->setup($config);
249  }
250 
251  // prepare any injectors
252  foreach ($this->modules as $module) {
253  $n = array();
254  foreach ($module->info_injector as $i => $injector) {
255  if (!is_object($injector)) {
256  $class = "HTMLPurifier_Injector_$injector";
257  $injector = new $class;
258  }
259  $n[$injector->name] = $injector;
260  }
261  $module->info_injector = $n;
262  }
263 
264  // setup lookup table based on all valid modules
265  foreach ($this->modules as $module) {
266  foreach ($module->info as $name => $def) {
267  if (!isset($this->elementLookup[$name])) {
268  $this->elementLookup[$name] = array();
269  }
270  $this->elementLookup[$name][] = $module->name;
271  }
272  }
273 
274  // note the different choice
275  $this->contentSets = new HTMLPurifier_ContentSets(
276  // content set assembly deals with all possible modules,
277  // not just ones deemed to be "safe"
278  $this->modules
279  );
280  $this->attrCollections = new HTMLPurifier_AttrCollections(
281  $this->attrTypes,
282  // there is no way to directly disable a global attribute,
283  // but using AllowedAttributes or simply not including
284  // the module in your custom doctype should be sufficient
285  $this->modules
286  );
287  }
288 
293  public function processModule($module) {
294  if (!isset($this->registeredModules[$module]) || is_object($module)) {
295  $this->registerModule($module);
296  }
297  $this->modules[$module] = $this->registeredModules[$module];
298  }
299 
304  public function getElements() {
305 
306  $elements = array();
307  foreach ($this->modules as $module) {
308  if (!$this->trusted && !$module->safe) continue;
309  foreach ($module->info as $name => $v) {
310  if (isset($elements[$name])) continue;
311  $elements[$name] = $this->getElement($name);
312  }
313  }
314 
315  // remove dud elements, this happens when an element that
316  // appeared to be safe actually wasn't
317  foreach ($elements as $n => $v) {
318  if ($v === false) unset($elements[$n]);
319  }
320 
321  return $elements;
322 
323  }
324 
335  public function getElement($name, $trusted = null) {
336 
337  if (!isset($this->elementLookup[$name])) {
338  return false;
339  }
340 
341  // setup global state variables
342  $def = false;
343  if ($trusted === null) $trusted = $this->trusted;
344 
345  // iterate through each module that has registered itself to this
346  // element
347  foreach($this->elementLookup[$name] as $module_name) {
348 
349  $module = $this->modules[$module_name];
350 
351  // refuse to create/merge from a module that is deemed unsafe--
352  // pretend the module doesn't exist--when trusted mode is not on.
353  if (!$trusted && !$module->safe) {
354  continue;
355  }
356 
357  // clone is used because, ideally speaking, the original
358  // definition should not be modified. Usually, this will
359  // make no difference, but for consistency's sake
360  $new_def = clone $module->info[$name];
361 
362  if (!$def && $new_def->standalone) {
363  $def = $new_def;
364  } elseif ($def) {
365  // This will occur even if $new_def is standalone. In practice,
366  // this will usually result in a full replacement.
367  $def->mergeIn($new_def);
368  } else {
369  // :TODO:
370  // non-standalone definitions that don't have a standalone
371  // to merge into could be deferred to the end
372  // HOWEVER, it is perfectly valid for a non-standalone
373  // definition to lack a standalone definition, even
374  // after all processing: this allows us to safely
375  // specify extra attributes for elements that may not be
376  // enabled all in one place. In particular, this might
377  // be the case for trusted elements. WARNING: care must
378  // be taken that the /extra/ definitions are all safe.
379  continue;
380  }
381 
382  // attribute value expansions
383  $this->attrCollections->performInclusions($def->attr);
384  $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
385 
386  // descendants_are_inline, for ChildDef_Chameleon
387  if (is_string($def->content_model) &&
388  strpos($def->content_model, 'Inline') !== false) {
389  if ($name != 'del' && $name != 'ins') {
390  // this is for you, ins/del
391  $def->descendants_are_inline = true;
392  }
393  }
394 
395  $this->contentSets->generateChildDef($def, $module);
396  }
397 
398  // This can occur if there is a blank definition, but no base to
399  // mix it in with
400  if (!$def) return false;
401 
402  // add information on required attributes
403  foreach ($def->attr as $attr_name => $attr_def) {
404  if ($attr_def->required) {
405  $def->required_attr[] = $attr_name;
406  }
407  }
408 
409  return $def;
410 
411  }
412 
413 }
414 
415 // vim: et sw=4 sts=4