ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
Config.php
Go to the documentation of this file.
1 <?php
2 
18 {
19 
24  public $version = '4.6.0';
25 
31  public $autoFinalize = true;
32 
33  // protected member variables
34 
40  protected $serials = array();
41 
46  protected $serial;
47 
52  protected $parser = null;
53 
60  public $def;
61 
66  protected $definitions;
67 
72  protected $finalized = false;
73 
78  protected $plist;
79 
84  private $aliasMode;
85 
92  public $chatty = true;
93 
98  private $lock;
99 
106  public function __construct($definition, $parent = null)
107  {
108  $parent = $parent ? $parent : $definition->defaultPlist;
109  $this->plist = new HTMLPurifier_PropertyList($parent);
110  $this->def = $definition; // keep a copy around for checking
111  $this->parser = new HTMLPurifier_VarParser_Flexible();
112  }
113 
123  public static function create($config, $schema = null)
124  {
125  if ($config instanceof HTMLPurifier_Config) {
126  // pass-through
127  return $config;
128  }
129  if (!$schema) {
131  } else {
132  $ret = new HTMLPurifier_Config($schema);
133  }
134  if (is_string($config)) {
135  $ret->loadIni($config);
136  } elseif (is_array($config)) $ret->loadArray($config);
137  return $ret;
138  }
139 
145  public static function inherit(HTMLPurifier_Config $config)
146  {
147  return new HTMLPurifier_Config($config->def, $config->plist);
148  }
149 
154  public static function createDefault()
155  {
156  $definition = HTMLPurifier_ConfigSchema::instance();
157  $config = new HTMLPurifier_Config($definition);
158  return $config;
159  }
160 
169  public function get($key, $a = null)
170  {
171  if ($a !== null) {
172  $this->triggerError(
173  "Using deprecated API: use \$config->get('$key.$a') instead",
174  E_USER_WARNING
175  );
176  $key = "$key.$a";
177  }
178  if (!$this->finalized) {
179  $this->autoFinalize();
180  }
181  if (!isset($this->def->info[$key])) {
182  // can't add % due to SimpleTest bug
183  $this->triggerError(
184  'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
185  E_USER_WARNING
186  );
187  return;
188  }
189  if (isset($this->def->info[$key]->isAlias)) {
190  $d = $this->def->info[$key];
191  $this->triggerError(
192  'Cannot get value from aliased directive, use real name ' . $d->key,
193  E_USER_ERROR
194  );
195  return;
196  }
197  if ($this->lock) {
198  list($ns) = explode('.', $key);
199  if ($ns !== $this->lock) {
200  $this->triggerError(
201  'Cannot get value of namespace ' . $ns . ' when lock for ' .
202  $this->lock .
203  ' is active, this probably indicates a Definition setup method ' .
204  'is accessing directives that are not within its namespace',
205  E_USER_ERROR
206  );
207  return;
208  }
209  }
210  return $this->plist->get($key);
211  }
212 
220  public function getBatch($namespace)
221  {
222  if (!$this->finalized) {
223  $this->autoFinalize();
224  }
225  $full = $this->getAll();
226  if (!isset($full[$namespace])) {
227  $this->triggerError(
228  'Cannot retrieve undefined namespace ' .
229  htmlspecialchars($namespace),
230  E_USER_WARNING
231  );
232  return;
233  }
234  return $full[$namespace];
235  }
236 
247  public function getBatchSerial($namespace)
248  {
249  if (empty($this->serials[$namespace])) {
250  $batch = $this->getBatch($namespace);
251  unset($batch['DefinitionRev']);
252  $this->serials[$namespace] = sha1(serialize($batch));
253  }
254  return $this->serials[$namespace];
255  }
256 
263  public function getSerial()
264  {
265  if (empty($this->serial)) {
266  $this->serial = sha1(serialize($this->getAll()));
267  }
268  return $this->serial;
269  }
270 
276  public function getAll()
277  {
278  if (!$this->finalized) {
279  $this->autoFinalize();
280  }
281  $ret = array();
282  foreach ($this->plist->squash() as $name => $value) {
283  list($ns, $key) = explode('.', $name, 2);
284  $ret[$ns][$key] = $value;
285  }
286  return $ret;
287  }
288 
296  public function set($key, $value, $a = null)
297  {
298  if (strpos($key, '.') === false) {
299  $namespace = $key;
300  $directive = $value;
301  $value = $a;
302  $key = "$key.$directive";
303  $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
304  } else {
305  list($namespace) = explode('.', $key);
306  }
307  if ($this->isFinalized('Cannot set directive after finalization')) {
308  return;
309  }
310  if (!isset($this->def->info[$key])) {
311  $this->triggerError(
312  'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
313  E_USER_WARNING
314  );
315  return;
316  }
317  $def = $this->def->info[$key];
318 
319  if (isset($def->isAlias)) {
320  if ($this->aliasMode) {
321  $this->triggerError(
322  'Double-aliases not allowed, please fix '.
323  'ConfigSchema bug with' . $key,
324  E_USER_ERROR
325  );
326  return;
327  }
328  $this->aliasMode = true;
329  $this->set($def->key, $value);
330  $this->aliasMode = false;
331  $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
332  return;
333  }
334 
335  // Raw type might be negative when using the fully optimized form
336  // of stdclass, which indicates allow_null == true
337  $rtype = is_int($def) ? $def : $def->type;
338  if ($rtype < 0) {
339  $type = -$rtype;
340  $allow_null = true;
341  } else {
342  $type = $rtype;
343  $allow_null = isset($def->allow_null);
344  }
345 
346  try {
347  $value = $this->parser->parse($value, $type, $allow_null);
348  } catch (HTMLPurifier_VarParserException $e) {
349  $this->triggerError(
350  'Value for ' . $key . ' is of invalid type, should be ' .
352  E_USER_WARNING
353  );
354  return;
355  }
356  if (is_string($value) && is_object($def)) {
357  // resolve value alias if defined
358  if (isset($def->aliases[$value])) {
359  $value = $def->aliases[$value];
360  }
361  // check to see if the value is allowed
362  if (isset($def->allowed) && !isset($def->allowed[$value])) {
363  $this->triggerError(
364  'Value not supported, valid values are: ' .
365  $this->_listify($def->allowed),
366  E_USER_WARNING
367  );
368  return;
369  }
370  }
371  $this->plist->set($key, $value);
372 
373  // reset definitions if the directives they depend on changed
374  // this is a very costly process, so it's discouraged
375  // with finalization
376  if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
377  $this->definitions[$namespace] = null;
378  }
379 
380  $this->serials[$namespace] = false;
381  }
382 
390  private function _listify($lookup)
391  {
392  $list = array();
393  foreach ($lookup as $name => $b) {
394  $list[] = $name;
395  }
396  return implode(', ', $list);
397  }
398 
413  public function getHTMLDefinition($raw = false, $optimized = false)
414  {
415  return $this->getDefinition('HTML', $raw, $optimized);
416  }
417 
432  public function getCSSDefinition($raw = false, $optimized = false)
433  {
434  return $this->getDefinition('CSS', $raw, $optimized);
435  }
436 
451  public function getURIDefinition($raw = false, $optimized = false)
452  {
453  return $this->getDefinition('URI', $raw, $optimized);
454  }
455 
473  public function getDefinition($type, $raw = false, $optimized = false)
474  {
475  if ($optimized && !$raw) {
476  throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
477  }
478  if (!$this->finalized) {
479  $this->autoFinalize();
480  }
481  // temporarily suspend locks, so we can handle recursive definition calls
482  $lock = $this->lock;
483  $this->lock = null;
485  $cache = $factory->create($type, $this);
486  $this->lock = $lock;
487  if (!$raw) {
488  // full definition
489  // ---------------
490  // check if definition is in memory
491  if (!empty($this->definitions[$type])) {
492  $def = $this->definitions[$type];
493  // check if the definition is setup
494  if ($def->setup) {
495  return $def;
496  } else {
497  $def->setup($this);
498  if ($def->optimized) {
499  $cache->add($def, $this);
500  }
501  return $def;
502  }
503  }
504  // check if definition is in cache
505  $def = $cache->get($this);
506  if ($def) {
507  // definition in cache, save to memory and return it
508  $this->definitions[$type] = $def;
509  return $def;
510  }
511  // initialize it
512  $def = $this->initDefinition($type);
513  // set it up
514  $this->lock = $type;
515  $def->setup($this);
516  $this->lock = null;
517  // save in cache
518  $cache->add($def, $this);
519  // return it
520  return $def;
521  } else {
522  // raw definition
523  // --------------
524  // check preconditions
525  $def = null;
526  if ($optimized) {
527  if (is_null($this->get($type . '.DefinitionID'))) {
528  // fatally error out if definition ID not set
529  throw new HTMLPurifier_Exception(
530  "Cannot retrieve raw version without specifying %$type.DefinitionID"
531  );
532  }
533  }
534  if (!empty($this->definitions[$type])) {
535  $def = $this->definitions[$type];
536  if ($def->setup && !$optimized) {
537  $extra = $this->chatty ?
538  " (try moving this code block earlier in your initialization)" :
539  "";
540  throw new HTMLPurifier_Exception(
541  "Cannot retrieve raw definition after it has already been setup" .
542  $extra
543  );
544  }
545  if ($def->optimized === null) {
546  $extra = $this->chatty ? " (try flushing your cache)" : "";
547  throw new HTMLPurifier_Exception(
548  "Optimization status of definition is unknown" . $extra
549  );
550  }
551  if ($def->optimized !== $optimized) {
552  $msg = $optimized ? "optimized" : "unoptimized";
553  $extra = $this->chatty ?
554  " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
555  : "";
556  throw new HTMLPurifier_Exception(
557  "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
558  );
559  }
560  }
561  // check if definition was in memory
562  if ($def) {
563  if ($def->setup) {
564  // invariant: $optimized === true (checked above)
565  return null;
566  } else {
567  return $def;
568  }
569  }
570  // if optimized, check if definition was in cache
571  // (because we do the memory check first, this formulation
572  // is prone to cache slamming, but I think
573  // guaranteeing that either /all/ of the raw
574  // setup code or /none/ of it is run is more important.)
575  if ($optimized) {
576  // This code path only gets run once; once we put
577  // something in $definitions (which is guaranteed by the
578  // trailing code), we always short-circuit above.
579  $def = $cache->get($this);
580  if ($def) {
581  // save the full definition for later, but don't
582  // return it yet
583  $this->definitions[$type] = $def;
584  return null;
585  }
586  }
587  // check invariants for creation
588  if (!$optimized) {
589  if (!is_null($this->get($type . '.DefinitionID'))) {
590  if ($this->chatty) {
591  $this->triggerError(
592  'Due to a documentation error in previous version of HTML Purifier, your ' .
593  'definitions are not being cached. If this is OK, you can remove the ' .
594  '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
595  'modify your code to use maybeGetRawDefinition, and test if the returned ' .
596  'value is null before making any edits (if it is null, that means that a ' .
597  'cached version is available, and no raw operations are necessary). See ' .
598  '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
599  'Customize</a> for more details',
600  E_USER_WARNING
601  );
602  } else {
603  $this->triggerError(
604  "Useless DefinitionID declaration",
605  E_USER_WARNING
606  );
607  }
608  }
609  }
610  // initialize it
611  $def = $this->initDefinition($type);
612  $def->optimized = $optimized;
613  return $def;
614  }
615  throw new HTMLPurifier_Exception("The impossible happened!");
616  }
617 
626  private function initDefinition($type)
627  {
628  // quick checks failed, let's create the object
629  if ($type == 'HTML') {
631  } elseif ($type == 'CSS') {
633  } elseif ($type == 'URI') {
635  } else {
636  throw new HTMLPurifier_Exception(
637  "Definition of $type type not supported"
638  );
639  }
640  $this->definitions[$type] = $def;
641  return $def;
642  }
643 
644  public function maybeGetRawDefinition($name)
645  {
646  return $this->getDefinition($name, true, true);
647  }
648 
649  public function maybeGetRawHTMLDefinition()
650  {
651  return $this->getDefinition('HTML', true, true);
652  }
653 
654  public function maybeGetRawCSSDefinition()
655  {
656  return $this->getDefinition('CSS', true, true);
657  }
658 
659  public function maybeGetRawURIDefinition()
660  {
661  return $this->getDefinition('URI', true, true);
662  }
663 
670  public function loadArray($config_array)
671  {
672  if ($this->isFinalized('Cannot load directives after finalization')) {
673  return;
674  }
675  foreach ($config_array as $key => $value) {
676  $key = str_replace('_', '.', $key);
677  if (strpos($key, '.') !== false) {
678  $this->set($key, $value);
679  } else {
680  $namespace = $key;
681  $namespace_values = $value;
682  foreach ($namespace_values as $directive => $value2) {
683  $this->set($namespace .'.'. $directive, $value2);
684  }
685  }
686  }
687  }
688 
699  public static function getAllowedDirectivesForForm($allowed, $schema = null)
700  {
701  if (!$schema) {
703  }
704  if ($allowed !== true) {
705  if (is_string($allowed)) {
706  $allowed = array($allowed);
707  }
708  $allowed_ns = array();
709  $allowed_directives = array();
710  $blacklisted_directives = array();
711  foreach ($allowed as $ns_or_directive) {
712  if (strpos($ns_or_directive, '.') !== false) {
713  // directive
714  if ($ns_or_directive[0] == '-') {
715  $blacklisted_directives[substr($ns_or_directive, 1)] = true;
716  } else {
717  $allowed_directives[$ns_or_directive] = true;
718  }
719  } else {
720  // namespace
721  $allowed_ns[$ns_or_directive] = true;
722  }
723  }
724  }
725  $ret = array();
726  foreach ($schema->info as $key => $def) {
727  list($ns, $directive) = explode('.', $key, 2);
728  if ($allowed !== true) {
729  if (isset($blacklisted_directives["$ns.$directive"])) {
730  continue;
731  }
732  if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
733  continue;
734  }
735  }
736  if (isset($def->isAlias)) {
737  continue;
738  }
739  if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
740  continue;
741  }
742  $ret[] = array($ns, $directive);
743  }
744  return $ret;
745  }
746 
759  public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
760  {
761  $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
762  $config = HTMLPurifier_Config::create($ret, $schema);
763  return $config;
764  }
765 
774  public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
775  {
776  $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
777  $this->loadArray($ret);
778  }
779 
792  public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
793  {
794  if ($index !== false) {
795  $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
796  }
797  $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
798 
799  $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
800  $ret = array();
801  foreach ($allowed as $key) {
802  list($ns, $directive) = $key;
803  $skey = "$ns.$directive";
804  if (!empty($array["Null_$skey"])) {
805  $ret[$ns][$directive] = null;
806  continue;
807  }
808  if (!isset($array[$skey])) {
809  continue;
810  }
811  $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
812  $ret[$ns][$directive] = $value;
813  }
814  return $ret;
815  }
816 
822  public function loadIni($filename)
823  {
824  if ($this->isFinalized('Cannot load directives after finalization')) {
825  return;
826  }
827  $array = parse_ini_file($filename, true);
828  $this->loadArray($array);
829  }
830 
838  public function isFinalized($error = false)
839  {
840  if ($this->finalized && $error) {
841  $this->triggerError($error, E_USER_ERROR);
842  }
843  return $this->finalized;
844  }
845 
850  public function autoFinalize()
851  {
852  if ($this->autoFinalize) {
853  $this->finalize();
854  } else {
855  $this->plist->squash(true);
856  }
857  }
858 
862  public function finalize()
863  {
864  $this->finalized = true;
865  $this->parser = null;
866  }
867 
875  protected function triggerError($msg, $no)
876  {
877  // determine previous stack frame
878  $extra = '';
879  if ($this->chatty) {
880  $trace = debug_backtrace();
881  // zip(tail(trace), trace) -- but PHP is not Haskell har har
882  for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
883  // XXX this is not correct on some versions of HTML Purifier
884  if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
885  continue;
886  }
887  $frame = $trace[$i];
888  $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
889  break;
890  }
891  }
892  trigger_error($msg . $extra, $no);
893  }
894 
901  public function serialize()
902  {
903  $this->getDefinition('HTML');
904  $this->getDefinition('CSS');
905  $this->getDefinition('URI');
906  return serialize($this);
907  }
908 
909 }
910 
911 // vim: et sw=4 sts=4