4
* Configuration object that triggers customizable behavior.
6
* @warning This class is strongly defined: that means that the class
7
* will fail if an undefined directive is retrieved or set.
9
* @note Many classes that could (although many times don't) use the
10
* configuration object make it a mandatory parameter. This is
11
* because a configuration object should always be forwarded,
12
* otherwise, you run the risk of missing a parameter and then
13
* being stumped when a configuration directive doesn't work.
15
* @todo Reconsider some of the public member variables
17
class HTMLPurifier_Config
21
* HTML Purifier's version
23
public $version = '4.5.0';
26
* Bool indicator whether or not to automatically finalize
27
* the object if a read operation is done
29
public $autoFinalize = true;
31
// protected member variables
34
* Namespace indexed array of serials for specific namespaces (see
35
* getSerial() for more info).
37
protected $serials = array();
40
* Serial for entire configuration object
45
* Parser for variables
47
protected $parser = null;
50
* Reference HTMLPurifier_ConfigSchema for value checking
51
* @note This is public for introspective purposes. Please don't
57
* Indexed array of definitions
59
protected $definitions;
62
* Bool indicator whether or not config is finalized
64
protected $finalized = false;
67
* Property list containing configuration directives.
72
* Whether or not a set is taking place due to an
78
* Set to false if you do not want line and file numbers in errors
79
* (useful when unit testing). This will also compress some errors
82
public $chatty = true;
85
* Current lock; only gets to this namespace are allowed.
90
* @param $definition HTMLPurifier_ConfigSchema that defines what directives
93
public function __construct($definition, $parent = null) {
94
$parent = $parent ? $parent : $definition->defaultPlist;
95
$this->plist = new HTMLPurifier_PropertyList($parent);
96
$this->def = $definition; // keep a copy around for checking
97
$this->parser = new HTMLPurifier_VarParser_Flexible();
101
* Convenience constructor that creates a config object based on a mixed var
102
* @param mixed $config Variable that defines the state of the config
103
* object. Can be: a HTMLPurifier_Config() object,
104
* an array of directives based on loadArray(),
105
* or a string filename of an ini file.
106
* @param HTMLPurifier_ConfigSchema Schema object
107
* @return Configured HTMLPurifier_Config object
109
public static function create($config, $schema = null) {
110
if ($config instanceof HTMLPurifier_Config) {
115
$ret = HTMLPurifier_Config::createDefault();
117
$ret = new HTMLPurifier_Config($schema);
119
if (is_string($config)) $ret->loadIni($config);
120
elseif (is_array($config)) $ret->loadArray($config);
125
* Creates a new config object that inherits from a previous one.
126
* @param HTMLPurifier_Config $config Configuration object to inherit
128
* @return HTMLPurifier_Config object with $config as its parent.
130
public static function inherit(HTMLPurifier_Config $config) {
131
return new HTMLPurifier_Config($config->def, $config->plist);
135
* Convenience constructor that creates a default configuration object.
136
* @return Default HTMLPurifier_Config object.
138
public static function createDefault() {
139
$definition = HTMLPurifier_ConfigSchema::instance();
140
$config = new HTMLPurifier_Config($definition);
145
* Retreives a value from the configuration.
146
* @param $key String key
148
public function get($key, $a = null) {
150
$this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING);
153
if (!$this->finalized) $this->autoFinalize();
154
if (!isset($this->def->info[$key])) {
155
// can't add % due to SimpleTest bug
156
$this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
160
if (isset($this->def->info[$key]->isAlias)) {
161
$d = $this->def->info[$key];
162
$this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key,
167
list($ns) = explode('.', $key);
168
if ($ns !== $this->lock) {
169
$this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR);
173
return $this->plist->get($key);
177
* Retreives an array of directives to values from a given namespace
178
* @param $namespace String namespace
180
public function getBatch($namespace) {
181
if (!$this->finalized) $this->autoFinalize();
182
$full = $this->getAll();
183
if (!isset($full[$namespace])) {
184
$this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
188
return $full[$namespace];
192
* Returns a SHA-1 signature of a segment of the configuration object
193
* that uniquely identifies that particular configuration
194
* @note Revision is handled specially and is removed from the batch
196
* @param $namespace Namespace to get serial for
198
public function getBatchSerial($namespace) {
199
if (empty($this->serials[$namespace])) {
200
$batch = $this->getBatch($namespace);
201
unset($batch['DefinitionRev']);
202
$this->serials[$namespace] = sha1(serialize($batch));
204
return $this->serials[$namespace];
208
* Returns a SHA-1 signature for the entire configuration object
209
* that uniquely identifies that particular configuration
211
public function getSerial() {
212
if (empty($this->serial)) {
213
$this->serial = sha1(serialize($this->getAll()));
215
return $this->serial;
219
* Retrieves all directives, organized by namespace
220
* @warning This is a pretty inefficient function, avoid if you can
222
public function getAll() {
223
if (!$this->finalized) $this->autoFinalize();
225
foreach ($this->plist->squash() as $name => $value) {
226
list($ns, $key) = explode('.', $name, 2);
227
$ret[$ns][$key] = $value;
233
* Sets a value to configuration.
234
* @param $key String key
235
* @param $value Mixed value
237
public function set($key, $value, $a = null) {
238
if (strpos($key, '.') === false) {
242
$key = "$key.$directive";
243
$this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
245
list($namespace) = explode('.', $key);
247
if ($this->isFinalized('Cannot set directive after finalization')) return;
248
if (!isset($this->def->info[$key])) {
249
$this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
253
$def = $this->def->info[$key];
255
if (isset($def->isAlias)) {
256
if ($this->aliasMode) {
257
$this->triggerError('Double-aliases not allowed, please fix '.
258
'ConfigSchema bug with' . $key, E_USER_ERROR);
261
$this->aliasMode = true;
262
$this->set($def->key, $value);
263
$this->aliasMode = false;
264
$this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
268
// Raw type might be negative when using the fully optimized form
269
// of stdclass, which indicates allow_null == true
270
$rtype = is_int($def) ? $def : $def->type;
276
$allow_null = isset($def->allow_null);
280
$value = $this->parser->parse($value, $type, $allow_null);
281
} catch (HTMLPurifier_VarParserException $e) {
282
$this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
285
if (is_string($value) && is_object($def)) {
286
// resolve value alias if defined
287
if (isset($def->aliases[$value])) {
288
$value = $def->aliases[$value];
290
// check to see if the value is allowed
291
if (isset($def->allowed) && !isset($def->allowed[$value])) {
292
$this->triggerError('Value not supported, valid values are: ' .
293
$this->_listify($def->allowed), E_USER_WARNING);
297
$this->plist->set($key, $value);
299
// reset definitions if the directives they depend on changed
300
// this is a very costly process, so it's discouraged
302
if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
303
$this->definitions[$namespace] = null;
306
$this->serials[$namespace] = false;
310
* Convenience function for error reporting
312
private function _listify($lookup) {
314
foreach ($lookup as $name => $b) $list[] = $name;
315
return implode(', ', $list);
319
* Retrieves object reference to the HTML definition.
320
* @param $raw Return a copy that has not been setup yet. Must be
321
* called before it's been setup, otherwise won't work.
322
* @param $optimized If true, this method may return null, to
323
* indicate that a cached version of the modified
324
* definition object is available and no further edits
325
* are necessary. Consider using
326
* maybeGetRawHTMLDefinition, which is more explicitly
329
public function getHTMLDefinition($raw = false, $optimized = false) {
330
return $this->getDefinition('HTML', $raw, $optimized);
334
* Retrieves object reference to the CSS definition
335
* @param $raw Return a copy that has not been setup yet. Must be
336
* called before it's been setup, otherwise won't work.
337
* @param $optimized If true, this method may return null, to
338
* indicate that a cached version of the modified
339
* definition object is available and no further edits
340
* are necessary. Consider using
341
* maybeGetRawCSSDefinition, which is more explicitly
344
public function getCSSDefinition($raw = false, $optimized = false) {
345
return $this->getDefinition('CSS', $raw, $optimized);
349
* Retrieves object reference to the URI definition
350
* @param $raw Return a copy that has not been setup yet. Must be
351
* called before it's been setup, otherwise won't work.
352
* @param $optimized If true, this method may return null, to
353
* indicate that a cached version of the modified
354
* definition object is available and no further edits
355
* are necessary. Consider using
356
* maybeGetRawURIDefinition, which is more explicitly
359
public function getURIDefinition($raw = false, $optimized = false) {
360
return $this->getDefinition('URI', $raw, $optimized);
364
* Retrieves a definition
365
* @param $type Type of definition: HTML, CSS, etc
366
* @param $raw Whether or not definition should be returned raw
367
* @param $optimized Only has an effect when $raw is true. Whether
368
* or not to return null if the result is already present in
369
* the cache. This is off by default for backwards
370
* compatibility reasons, but you need to do things this
371
* way in order to ensure that caching is done properly.
372
* Check out enduser-customize.html for more details.
373
* We probably won't ever change this default, as much as the
374
* maybe semantics is the "right thing to do."
376
public function getDefinition($type, $raw = false, $optimized = false) {
377
if ($optimized && !$raw) {
378
throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
380
if (!$this->finalized) $this->autoFinalize();
381
// temporarily suspend locks, so we can handle recursive definition calls
384
$factory = HTMLPurifier_DefinitionCacheFactory::instance();
385
$cache = $factory->create($type, $this);
390
// check if definition is in memory
391
if (!empty($this->definitions[$type])) {
392
$def = $this->definitions[$type];
393
// check if the definition is setup
398
if ($def->optimized) $cache->add($def, $this);
402
// check if definition is in cache
403
$def = $cache->get($this);
405
// definition in cache, save to memory and return it
406
$this->definitions[$type] = $def;
410
$def = $this->initDefinition($type);
416
$cache->add($def, $this);
422
// check preconditions
425
if (is_null($this->get($type . '.DefinitionID'))) {
426
// fatally error out if definition ID not set
427
throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
430
if (!empty($this->definitions[$type])) {
431
$def = $this->definitions[$type];
432
if ($def->setup && !$optimized) {
433
$extra = $this->chatty ? " (try moving this code block earlier in your initialization)" : "";
434
throw new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup" . $extra);
436
if ($def->optimized === null) {
437
$extra = $this->chatty ? " (try flushing your cache)" : "";
438
throw new HTMLPurifier_Exception("Optimization status of definition is unknown" . $extra);
440
if ($def->optimized !== $optimized) {
441
$msg = $optimized ? "optimized" : "unoptimized";
442
$extra = $this->chatty ? " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" : "";
443
throw new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra);
446
// check if definition was in memory
449
// invariant: $optimized === true (checked above)
455
// if optimized, check if definition was in cache
456
// (because we do the memory check first, this formulation
457
// is prone to cache slamming, but I think
458
// guaranteeing that either /all/ of the raw
459
// setup code or /none/ of it is run is more important.)
461
// This code path only gets run once; once we put
462
// something in $definitions (which is guaranteed by the
463
// trailing code), we always short-circuit above.
464
$def = $cache->get($this);
466
// save the full definition for later, but don't
468
$this->definitions[$type] = $def;
472
// check invariants for creation
474
if (!is_null($this->get($type . '.DefinitionID'))) {
476
$this->triggerError("Due to a documentation error in previous version of HTML Purifier, your definitions are not being cached. If this is OK, you can remove the %$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, modify your code to use maybeGetRawDefinition, and test if the returned value is null before making any edits (if it is null, that means that a cached version is available, and no raw operations are necessary). See <a href='http://htmlpurifier.org/docs/enduser-customize.html#optimized'>Customize</a> for more details", E_USER_WARNING);
478
$this->triggerError("Useless DefinitionID declaration", E_USER_WARNING);
483
$def = $this->initDefinition($type);
484
$def->optimized = $optimized;
487
throw new HTMLPurifier_Exception("The impossible happened!");
490
private function initDefinition($type) {
491
// quick checks failed, let's create the object
492
if ($type == 'HTML') {
493
$def = new HTMLPurifier_HTMLDefinition();
494
} elseif ($type == 'CSS') {
495
$def = new HTMLPurifier_CSSDefinition();
496
} elseif ($type == 'URI') {
497
$def = new HTMLPurifier_URIDefinition();
499
throw new HTMLPurifier_Exception("Definition of $type type not supported");
501
$this->definitions[$type] = $def;
505
public function maybeGetRawDefinition($name) {
506
return $this->getDefinition($name, true, true);
509
public function maybeGetRawHTMLDefinition() {
510
return $this->getDefinition('HTML', true, true);
513
public function maybeGetRawCSSDefinition() {
514
return $this->getDefinition('CSS', true, true);
517
public function maybeGetRawURIDefinition() {
518
return $this->getDefinition('URI', true, true);
522
* Loads configuration values from an array with the following structure:
523
* Namespace.Directive => Value
524
* @param $config_array Configuration associative array
526
public function loadArray($config_array) {
527
if ($this->isFinalized('Cannot load directives after finalization')) return;
528
foreach ($config_array as $key => $value) {
529
$key = str_replace('_', '.', $key);
530
if (strpos($key, '.') !== false) {
531
$this->set($key, $value);
534
$namespace_values = $value;
535
foreach ($namespace_values as $directive => $value) {
536
$this->set($namespace .'.'. $directive, $value);
543
* Returns a list of array(namespace, directive) for all directives
544
* that are allowed in a web-form context as per an allowed
545
* namespaces/directives list.
546
* @param $allowed List of allowed namespaces/directives
548
public static function getAllowedDirectivesForForm($allowed, $schema = null) {
550
$schema = HTMLPurifier_ConfigSchema::instance();
552
if ($allowed !== true) {
553
if (is_string($allowed)) $allowed = array($allowed);
554
$allowed_ns = array();
555
$allowed_directives = array();
556
$blacklisted_directives = array();
557
foreach ($allowed as $ns_or_directive) {
558
if (strpos($ns_or_directive, '.') !== false) {
560
if ($ns_or_directive[0] == '-') {
561
$blacklisted_directives[substr($ns_or_directive, 1)] = true;
563
$allowed_directives[$ns_or_directive] = true;
567
$allowed_ns[$ns_or_directive] = true;
572
foreach ($schema->info as $key => $def) {
573
list($ns, $directive) = explode('.', $key, 2);
574
if ($allowed !== true) {
575
if (isset($blacklisted_directives["$ns.$directive"])) continue;
576
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
578
if (isset($def->isAlias)) continue;
579
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
580
$ret[] = array($ns, $directive);
586
* Loads configuration values from $_GET/$_POST that were posted
588
* @param $array $_GET or $_POST array to import
589
* @param $index Index/name that the config variables are in
590
* @param $allowed List of allowed namespaces/directives
591
* @param $mq_fix Boolean whether or not to enable magic quotes fix
592
* @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy
594
public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
595
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
596
$config = HTMLPurifier_Config::create($ret, $schema);
601
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
602
* @note Same parameters as loadArrayFromForm
604
public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
605
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
606
$this->loadArray($ret);
610
* Prepares an array from a form into something usable for the more
611
* strict parts of HTMLPurifier_Config
613
public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
614
if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
615
$mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
617
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
619
foreach ($allowed as $key) {
620
list($ns, $directive) = $key;
621
$skey = "$ns.$directive";
622
if (!empty($array["Null_$skey"])) {
623
$ret[$ns][$directive] = null;
626
if (!isset($array[$skey])) continue;
627
$value = $mq ? stripslashes($array[$skey]) : $array[$skey];
628
$ret[$ns][$directive] = $value;
634
* Loads configuration values from an ini file
635
* @param $filename Name of ini file
637
public function loadIni($filename) {
638
if ($this->isFinalized('Cannot load directives after finalization')) return;
639
$array = parse_ini_file($filename, true);
640
$this->loadArray($array);
644
* Checks whether or not the configuration object is finalized.
645
* @param $error String error message, or false for no error
647
public function isFinalized($error = false) {
648
if ($this->finalized && $error) {
649
$this->triggerError($error, E_USER_ERROR);
651
return $this->finalized;
655
* Finalizes configuration only if auto finalize is on and not
658
public function autoFinalize() {
659
if ($this->autoFinalize) {
662
$this->plist->squash(true);
667
* Finalizes a configuration object, prohibiting further change
669
public function finalize() {
670
$this->finalized = true;
671
$this->parser = null;
675
* Produces a nicely formatted error message by supplying the
676
* stack frame information OUTSIDE of HTMLPurifier_Config.
678
protected function triggerError($msg, $no) {
679
// determine previous stack frame
682
$trace = debug_backtrace();
683
// zip(tail(trace), trace) -- but PHP is not Haskell har har
684
for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
685
// XXX this is not correct on some versions of HTML Purifier
686
if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
690
$extra = " invoked on line {$frame['line']} in file {$frame['file']}";
694
trigger_error($msg . $extra, $no);
698
* Returns a serialized form of the configuration object that can
701
public function serialize() {
702
$this->getDefinition('HTML');
703
$this->getDefinition('CSS');
704
$this->getDefinition('URI');
705
return serialize($this);
710
// vim: et sw=4 sts=4
4
* Configuration object that triggers customizable behavior.
6
* @warning This class is strongly defined: that means that the class
7
* will fail if an undefined directive is retrieved or set.
9
* @note Many classes that could (although many times don't) use the
10
* configuration object make it a mandatory parameter. This is
11
* because a configuration object should always be forwarded,
12
* otherwise, you run the risk of missing a parameter and then
13
* being stumped when a configuration directive doesn't work.
15
* @todo Reconsider some of the public member variables
17
class HTMLPurifier_Config
21
* HTML Purifier's version
24
public $version = '4.6.0';
27
* Whether or not to automatically finalize
28
* the object if a read operation is done.
31
public $autoFinalize = true;
33
// protected member variables
36
* Namespace indexed array of serials for specific namespaces.
37
* @see getSerial() for more info.
40
protected $serials = array();
43
* Serial for entire configuration object.
49
* Parser for variables.
50
* @type HTMLPurifier_VarParser_Flexible
52
protected $parser = null;
55
* Reference HTMLPurifier_ConfigSchema for value checking.
56
* @type HTMLPurifier_ConfigSchema
57
* @note This is public for introspective purposes. Please don't
63
* Indexed array of definitions.
64
* @type HTMLPurifier_Definition[]
66
protected $definitions;
69
* Whether or not config is finalized.
72
protected $finalized = false;
75
* Property list containing configuration directives.
81
* Whether or not a set is taking place due to an alias lookup.
87
* Set to false if you do not want line and file numbers in errors.
88
* (useful when unit testing). This will also compress some errors
92
public $chatty = true;
95
* Current lock; only gets to this namespace are allowed.
102
* @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
103
* what directives are allowed.
104
* @param HTMLPurifier_PropertyList $parent
106
public function __construct($definition, $parent = null)
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();
115
* Convenience constructor that creates a config object based on a mixed var
116
* @param mixed $config Variable that defines the state of the config
117
* object. Can be: a HTMLPurifier_Config() object,
118
* an array of directives based on loadArray(),
119
* or a string filename of an ini file.
120
* @param HTMLPurifier_ConfigSchema $schema Schema object
121
* @return HTMLPurifier_Config Configured object
123
public static function create($config, $schema = null)
125
if ($config instanceof HTMLPurifier_Config) {
130
$ret = HTMLPurifier_Config::createDefault();
132
$ret = new HTMLPurifier_Config($schema);
134
if (is_string($config)) {
135
$ret->loadIni($config);
136
} elseif (is_array($config)) $ret->loadArray($config);
141
* Creates a new config object that inherits from a previous one.
142
* @param HTMLPurifier_Config $config Configuration object to inherit from.
143
* @return HTMLPurifier_Config object with $config as its parent.
145
public static function inherit(HTMLPurifier_Config $config)
147
return new HTMLPurifier_Config($config->def, $config->plist);
151
* Convenience constructor that creates a default configuration object.
152
* @return HTMLPurifier_Config default object.
154
public static function createDefault()
156
$definition = HTMLPurifier_ConfigSchema::instance();
157
$config = new HTMLPurifier_Config($definition);
162
* Retrieves a value from the configuration.
164
* @param string $key String key
169
public function get($key, $a = null)
173
"Using deprecated API: use \$config->get('$key.$a') instead",
178
if (!$this->finalized) {
179
$this->autoFinalize();
181
if (!isset($this->def->info[$key])) {
182
// can't add % due to SimpleTest bug
184
'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
189
if (isset($this->def->info[$key]->isAlias)) {
190
$d = $this->def->info[$key];
192
'Cannot get value from aliased directive, use real name ' . $d->key,
198
list($ns) = explode('.', $key);
199
if ($ns !== $this->lock) {
201
'Cannot get value of namespace ' . $ns . ' when lock for ' .
203
' is active, this probably indicates a Definition setup method ' .
204
'is accessing directives that are not within its namespace',
210
return $this->plist->get($key);
214
* Retrieves an array of directives to values from a given namespace
216
* @param string $namespace String namespace
220
public function getBatch($namespace)
222
if (!$this->finalized) {
223
$this->autoFinalize();
225
$full = $this->getAll();
226
if (!isset($full[$namespace])) {
228
'Cannot retrieve undefined namespace ' .
229
htmlspecialchars($namespace),
234
return $full[$namespace];
238
* Returns a SHA-1 signature of a segment of the configuration object
239
* that uniquely identifies that particular configuration
241
* @param string $namespace Namespace to get serial for
244
* @note Revision is handled specially and is removed from the batch
247
public function getBatchSerial($namespace)
249
if (empty($this->serials[$namespace])) {
250
$batch = $this->getBatch($namespace);
251
unset($batch['DefinitionRev']);
252
$this->serials[$namespace] = sha1(serialize($batch));
254
return $this->serials[$namespace];
258
* Returns a SHA-1 signature for the entire configuration object
259
* that uniquely identifies that particular configuration
263
public function getSerial()
265
if (empty($this->serial)) {
266
$this->serial = sha1(serialize($this->getAll()));
268
return $this->serial;
272
* Retrieves all directives, organized by namespace
274
* @warning This is a pretty inefficient function, avoid if you can
276
public function getAll()
278
if (!$this->finalized) {
279
$this->autoFinalize();
282
foreach ($this->plist->squash() as $name => $value) {
283
list($ns, $key) = explode('.', $name, 2);
284
$ret[$ns][$key] = $value;
290
* Sets a value to configuration.
292
* @param string $key key
293
* @param mixed $value value
296
public function set($key, $value, $a = null)
298
if (strpos($key, '.') === false) {
302
$key = "$key.$directive";
303
$this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
305
list($namespace) = explode('.', $key);
307
if ($this->isFinalized('Cannot set directive after finalization')) {
310
if (!isset($this->def->info[$key])) {
312
'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
317
$def = $this->def->info[$key];
319
if (isset($def->isAlias)) {
320
if ($this->aliasMode) {
322
'Double-aliases not allowed, please fix '.
323
'ConfigSchema bug with' . $key,
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);
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;
343
$allow_null = isset($def->allow_null);
347
$value = $this->parser->parse($value, $type, $allow_null);
348
} catch (HTMLPurifier_VarParserException $e) {
350
'Value for ' . $key . ' is of invalid type, should be ' .
351
HTMLPurifier_VarParser::getTypeName($type),
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];
361
// check to see if the value is allowed
362
if (isset($def->allowed) && !isset($def->allowed[$value])) {
364
'Value not supported, valid values are: ' .
365
$this->_listify($def->allowed),
371
$this->plist->set($key, $value);
373
// reset definitions if the directives they depend on changed
374
// this is a very costly process, so it's discouraged
376
if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
377
$this->definitions[$namespace] = null;
380
$this->serials[$namespace] = false;
384
* Convenience function for error reporting
386
* @param array $lookup
390
private function _listify($lookup)
393
foreach ($lookup as $name => $b) {
396
return implode(', ', $list);
400
* Retrieves object reference to the HTML definition.
402
* @param bool $raw Return a copy that has not been setup yet. Must be
403
* called before it's been setup, otherwise won't work.
404
* @param bool $optimized If true, this method may return null, to
405
* indicate that a cached version of the modified
406
* definition object is available and no further edits
407
* are necessary. Consider using
408
* maybeGetRawHTMLDefinition, which is more explicitly
411
* @return HTMLPurifier_HTMLDefinition
413
public function getHTMLDefinition($raw = false, $optimized = false)
415
return $this->getDefinition('HTML', $raw, $optimized);
419
* Retrieves object reference to the CSS definition
421
* @param bool $raw Return a copy that has not been setup yet. Must be
422
* called before it's been setup, otherwise won't work.
423
* @param bool $optimized If true, this method may return null, to
424
* indicate that a cached version of the modified
425
* definition object is available and no further edits
426
* are necessary. Consider using
427
* maybeGetRawCSSDefinition, which is more explicitly
430
* @return HTMLPurifier_CSSDefinition
432
public function getCSSDefinition($raw = false, $optimized = false)
434
return $this->getDefinition('CSS', $raw, $optimized);
438
* Retrieves object reference to the URI definition
440
* @param bool $raw Return a copy that has not been setup yet. Must be
441
* called before it's been setup, otherwise won't work.
442
* @param bool $optimized If true, this method may return null, to
443
* indicate that a cached version of the modified
444
* definition object is available and no further edits
445
* are necessary. Consider using
446
* maybeGetRawURIDefinition, which is more explicitly
449
* @return HTMLPurifier_URIDefinition
451
public function getURIDefinition($raw = false, $optimized = false)
453
return $this->getDefinition('URI', $raw, $optimized);
457
* Retrieves a definition
459
* @param string $type Type of definition: HTML, CSS, etc
460
* @param bool $raw Whether or not definition should be returned raw
461
* @param bool $optimized Only has an effect when $raw is true. Whether
462
* or not to return null if the result is already present in
463
* the cache. This is off by default for backwards
464
* compatibility reasons, but you need to do things this
465
* way in order to ensure that caching is done properly.
466
* Check out enduser-customize.html for more details.
467
* We probably won't ever change this default, as much as the
468
* maybe semantics is the "right thing to do."
470
* @throws HTMLPurifier_Exception
471
* @return HTMLPurifier_Definition
473
public function getDefinition($type, $raw = false, $optimized = false)
475
if ($optimized && !$raw) {
476
throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
478
if (!$this->finalized) {
479
$this->autoFinalize();
481
// temporarily suspend locks, so we can handle recursive definition calls
484
$factory = HTMLPurifier_DefinitionCacheFactory::instance();
485
$cache = $factory->create($type, $this);
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
498
if ($def->optimized) {
499
$cache->add($def, $this);
504
// check if definition is in cache
505
$def = $cache->get($this);
507
// definition in cache, save to memory and return it
508
$this->definitions[$type] = $def;
512
$def = $this->initDefinition($type);
518
$cache->add($def, $this);
524
// check preconditions
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"
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)" :
540
throw new HTMLPurifier_Exception(
541
"Cannot retrieve raw definition after it has already been setup" .
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
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)"
556
throw new HTMLPurifier_Exception(
557
"Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
561
// check if definition was in memory
564
// invariant: $optimized === true (checked above)
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.)
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);
581
// save the full definition for later, but don't
583
$this->definitions[$type] = $def;
587
// check invariants for creation
589
if (!is_null($this->get($type . '.DefinitionID'))) {
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',
604
"Useless DefinitionID declaration",
611
$def = $this->initDefinition($type);
612
$def->optimized = $optimized;
615
throw new HTMLPurifier_Exception("The impossible happened!");
619
* Initialise definition
621
* @param string $type What type of definition to create
623
* @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
624
* @throws HTMLPurifier_Exception
626
private function initDefinition($type)
628
// quick checks failed, let's create the object
629
if ($type == 'HTML') {
630
$def = new HTMLPurifier_HTMLDefinition();
631
} elseif ($type == 'CSS') {
632
$def = new HTMLPurifier_CSSDefinition();
633
} elseif ($type == 'URI') {
634
$def = new HTMLPurifier_URIDefinition();
636
throw new HTMLPurifier_Exception(
637
"Definition of $type type not supported"
640
$this->definitions[$type] = $def;
644
public function maybeGetRawDefinition($name)
646
return $this->getDefinition($name, true, true);
649
public function maybeGetRawHTMLDefinition()
651
return $this->getDefinition('HTML', true, true);
654
public function maybeGetRawCSSDefinition()
656
return $this->getDefinition('CSS', true, true);
659
public function maybeGetRawURIDefinition()
661
return $this->getDefinition('URI', true, true);
665
* Loads configuration values from an array with the following structure:
666
* Namespace.Directive => Value
668
* @param array $config_array Configuration associative array
670
public function loadArray($config_array)
672
if ($this->isFinalized('Cannot load directives after finalization')) {
675
foreach ($config_array as $key => $value) {
676
$key = str_replace('_', '.', $key);
677
if (strpos($key, '.') !== false) {
678
$this->set($key, $value);
681
$namespace_values = $value;
682
foreach ($namespace_values as $directive => $value2) {
683
$this->set($namespace .'.'. $directive, $value2);
690
* Returns a list of array(namespace, directive) for all directives
691
* that are allowed in a web-form context as per an allowed
692
* namespaces/directives list.
694
* @param array $allowed List of allowed namespaces/directives
695
* @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
699
public static function getAllowedDirectivesForForm($allowed, $schema = null)
702
$schema = HTMLPurifier_ConfigSchema::instance();
704
if ($allowed !== true) {
705
if (is_string($allowed)) {
706
$allowed = array($allowed);
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) {
714
if ($ns_or_directive[0] == '-') {
715
$blacklisted_directives[substr($ns_or_directive, 1)] = true;
717
$allowed_directives[$ns_or_directive] = true;
721
$allowed_ns[$ns_or_directive] = true;
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"])) {
732
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
736
if (isset($def->isAlias)) {
739
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
742
$ret[] = array($ns, $directive);
748
* Loads configuration values from $_GET/$_POST that were posted
751
* @param array $array $_GET or $_POST array to import
752
* @param string|bool $index Index/name that the config variables are in
753
* @param array|bool $allowed List of allowed namespaces/directives
754
* @param bool $mq_fix Boolean whether or not to enable magic quotes fix
755
* @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
759
public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
761
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
762
$config = HTMLPurifier_Config::create($ret, $schema);
767
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
769
* @param array $array $_GET or $_POST array to import
770
* @param string|bool $index Index/name that the config variables are in
771
* @param array|bool $allowed List of allowed namespaces/directives
772
* @param bool $mq_fix Boolean whether or not to enable magic quotes fix
774
public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
776
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
777
$this->loadArray($ret);
781
* Prepares an array from a form into something usable for the more
782
* strict parts of HTMLPurifier_Config
784
* @param array $array $_GET or $_POST array to import
785
* @param string|bool $index Index/name that the config variables are in
786
* @param array|bool $allowed List of allowed namespaces/directives
787
* @param bool $mq_fix Boolean whether or not to enable magic quotes fix
788
* @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
792
public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
794
if ($index !== false) {
795
$array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
797
$mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
799
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
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;
808
if (!isset($array[$skey])) {
811
$value = $mq ? stripslashes($array[$skey]) : $array[$skey];
812
$ret[$ns][$directive] = $value;
818
* Loads configuration values from an ini file
820
* @param string $filename Name of ini file
822
public function loadIni($filename)
824
if ($this->isFinalized('Cannot load directives after finalization')) {
827
$array = parse_ini_file($filename, true);
828
$this->loadArray($array);
832
* Checks whether or not the configuration object is finalized.
834
* @param string|bool $error String error message, or false for no error
838
public function isFinalized($error = false)
840
if ($this->finalized && $error) {
841
$this->triggerError($error, E_USER_ERROR);
843
return $this->finalized;
847
* Finalizes configuration only if auto finalize is on and not
850
public function autoFinalize()
852
if ($this->autoFinalize) {
855
$this->plist->squash(true);
860
* Finalizes a configuration object, prohibiting further change
862
public function finalize()
864
$this->finalized = true;
865
$this->parser = null;
869
* Produces a nicely formatted error message by supplying the
870
* stack frame information OUTSIDE of HTMLPurifier_Config.
872
* @param string $msg An error message
873
* @param int $no An error number
875
protected function triggerError($msg, $no)
877
// determine previous stack frame
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') {
888
$extra = " invoked on line {$frame['line']} in file {$frame['file']}";
892
trigger_error($msg . $extra, $no);
896
* Returns a serialized form of the configuration object that can
901
public function serialize()
903
$this->getDefinition('HTML');
904
$this->getDefinition('CSS');
905
$this->getDefinition('URI');
906
return serialize($this);
911
// vim: et sw=4 sts=4