ILIAS  Release_4_4_x_branch Revision 61816
 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.SafeScripting') !== array()) {
232  $modules[] = 'SafeScripting';
233  }
234  if ($config->get('HTML.Nofollow')) {
235  $modules[] = 'Nofollow';
236  }
237  if ($config->get('HTML.TargetBlank')) {
238  $modules[] = 'TargetBlank';
239  }
240 
241  // merge in custom modules
242  $modules = array_merge($modules, $this->userModules);
243 
244  foreach ($modules as $module) {
245  $this->processModule($module);
246  $this->modules[$module]->setup($config);
247  }
248 
249  foreach ($this->doctype->tidyModules as $module) {
250  $this->processModule($module);
251  $this->modules[$module]->setup($config);
252  }
253 
254  // prepare any injectors
255  foreach ($this->modules as $module) {
256  $n = array();
257  foreach ($module->info_injector as $i => $injector) {
258  if (!is_object($injector)) {
259  $class = "HTMLPurifier_Injector_$injector";
260  $injector = new $class;
261  }
262  $n[$injector->name] = $injector;
263  }
264  $module->info_injector = $n;
265  }
266 
267  // setup lookup table based on all valid modules
268  foreach ($this->modules as $module) {
269  foreach ($module->info as $name => $def) {
270  if (!isset($this->elementLookup[$name])) {
271  $this->elementLookup[$name] = array();
272  }
273  $this->elementLookup[$name][] = $module->name;
274  }
275  }
276 
277  // note the different choice
278  $this->contentSets = new HTMLPurifier_ContentSets(
279  // content set assembly deals with all possible modules,
280  // not just ones deemed to be "safe"
281  $this->modules
282  );
283  $this->attrCollections = new HTMLPurifier_AttrCollections(
284  $this->attrTypes,
285  // there is no way to directly disable a global attribute,
286  // but using AllowedAttributes or simply not including
287  // the module in your custom doctype should be sufficient
288  $this->modules
289  );
290  }
291 
296  public function processModule($module) {
297  if (!isset($this->registeredModules[$module]) || is_object($module)) {
298  $this->registerModule($module);
299  }
300  $this->modules[$module] = $this->registeredModules[$module];
301  }
302 
307  public function getElements() {
308 
309  $elements = array();
310  foreach ($this->modules as $module) {
311  if (!$this->trusted && !$module->safe) continue;
312  foreach ($module->info as $name => $v) {
313  if (isset($elements[$name])) continue;
314  $elements[$name] = $this->getElement($name);
315  }
316  }
317 
318  // remove dud elements, this happens when an element that
319  // appeared to be safe actually wasn't
320  foreach ($elements as $n => $v) {
321  if ($v === false) unset($elements[$n]);
322  }
323 
324  return $elements;
325 
326  }
327 
338  public function getElement($name, $trusted = null) {
339 
340  if (!isset($this->elementLookup[$name])) {
341  return false;
342  }
343 
344  // setup global state variables
345  $def = false;
346  if ($trusted === null) $trusted = $this->trusted;
347 
348  // iterate through each module that has registered itself to this
349  // element
350  foreach($this->elementLookup[$name] as $module_name) {
351 
352  $module = $this->modules[$module_name];
353 
354  // refuse to create/merge from a module that is deemed unsafe--
355  // pretend the module doesn't exist--when trusted mode is not on.
356  if (!$trusted && !$module->safe) {
357  continue;
358  }
359 
360  // clone is used because, ideally speaking, the original
361  // definition should not be modified. Usually, this will
362  // make no difference, but for consistency's sake
363  $new_def = clone $module->info[$name];
364 
365  if (!$def && $new_def->standalone) {
366  $def = $new_def;
367  } elseif ($def) {
368  // This will occur even if $new_def is standalone. In practice,
369  // this will usually result in a full replacement.
370  $def->mergeIn($new_def);
371  } else {
372  // :TODO:
373  // non-standalone definitions that don't have a standalone
374  // to merge into could be deferred to the end
375  // HOWEVER, it is perfectly valid for a non-standalone
376  // definition to lack a standalone definition, even
377  // after all processing: this allows us to safely
378  // specify extra attributes for elements that may not be
379  // enabled all in one place. In particular, this might
380  // be the case for trusted elements. WARNING: care must
381  // be taken that the /extra/ definitions are all safe.
382  continue;
383  }
384 
385  // attribute value expansions
386  $this->attrCollections->performInclusions($def->attr);
387  $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
388 
389  // descendants_are_inline, for ChildDef_Chameleon
390  if (is_string($def->content_model) &&
391  strpos($def->content_model, 'Inline') !== false) {
392  if ($name != 'del' && $name != 'ins') {
393  // this is for you, ins/del
394  $def->descendants_are_inline = true;
395  }
396  }
397 
398  $this->contentSets->generateChildDef($def, $module);
399  }
400 
401  // This can occur if there is a blank definition, but no base to
402  // mix it in with
403  if (!$def) return false;
404 
405  // add information on required attributes
406  foreach ($def->attr as $attr_name => $attr_def) {
407  if ($attr_def->required) {
408  $def->required_attr[] = $attr_name;
409  }
410  }
411 
412  return $def;
413 
414  }
415 
416 }
417 
418 // vim: et sw=4 sts=4