~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to lib/htmlpurifier/HTMLPurifier/Config.php

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
 
3
 
/**
4
 
 * Configuration object that triggers customizable behavior.
5
 
 *
6
 
 * @warning This class is strongly defined: that means that the class
7
 
 *          will fail if an undefined directive is retrieved or set.
8
 
 *
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.
14
 
 *
15
 
 * @todo Reconsider some of the public member variables
16
 
 */
17
 
class HTMLPurifier_Config
18
 
{
19
 
 
20
 
    /**
21
 
     * HTML Purifier's version
22
 
     */
23
 
    public $version = '4.5.0';
24
 
 
25
 
    /**
26
 
     * Bool indicator whether or not to automatically finalize
27
 
     * the object if a read operation is done
28
 
     */
29
 
    public $autoFinalize = true;
30
 
 
31
 
    // protected member variables
32
 
 
33
 
    /**
34
 
     * Namespace indexed array of serials for specific namespaces (see
35
 
     * getSerial() for more info).
36
 
     */
37
 
    protected $serials = array();
38
 
 
39
 
    /**
40
 
     * Serial for entire configuration object
41
 
     */
42
 
    protected $serial;
43
 
 
44
 
    /**
45
 
     * Parser for variables
46
 
     */
47
 
    protected $parser = null;
48
 
 
49
 
    /**
50
 
     * Reference HTMLPurifier_ConfigSchema for value checking
51
 
     * @note This is public for introspective purposes. Please don't
52
 
     *       abuse!
53
 
     */
54
 
    public $def;
55
 
 
56
 
    /**
57
 
     * Indexed array of definitions
58
 
     */
59
 
    protected $definitions;
60
 
 
61
 
    /**
62
 
     * Bool indicator whether or not config is finalized
63
 
     */
64
 
    protected $finalized = false;
65
 
 
66
 
    /**
67
 
     * Property list containing configuration directives.
68
 
     */
69
 
    protected $plist;
70
 
 
71
 
    /**
72
 
     * Whether or not a set is taking place due to an
73
 
     * alias lookup.
74
 
     */
75
 
    private $aliasMode;
76
 
 
77
 
    /**
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
80
 
     * and exceptions.
81
 
     */
82
 
    public $chatty = true;
83
 
 
84
 
    /**
85
 
     * Current lock; only gets to this namespace are allowed.
86
 
     */
87
 
    private $lock;
88
 
 
89
 
    /**
90
 
     * @param $definition HTMLPurifier_ConfigSchema that defines what directives
91
 
     *                    are allowed.
92
 
     */
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();
98
 
    }
99
 
 
100
 
    /**
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
108
 
     */
109
 
    public static function create($config, $schema = null) {
110
 
        if ($config instanceof HTMLPurifier_Config) {
111
 
            // pass-through
112
 
            return $config;
113
 
        }
114
 
        if (!$schema) {
115
 
            $ret = HTMLPurifier_Config::createDefault();
116
 
        } else {
117
 
            $ret = new HTMLPurifier_Config($schema);
118
 
        }
119
 
        if (is_string($config)) $ret->loadIni($config);
120
 
        elseif (is_array($config)) $ret->loadArray($config);
121
 
        return $ret;
122
 
    }
123
 
 
124
 
    /**
125
 
     * Creates a new config object that inherits from a previous one.
126
 
     * @param HTMLPurifier_Config $config Configuration object to inherit
127
 
     *        from.
128
 
     * @return HTMLPurifier_Config object with $config as its parent.
129
 
     */
130
 
    public static function inherit(HTMLPurifier_Config $config) {
131
 
        return new HTMLPurifier_Config($config->def, $config->plist);
132
 
    }
133
 
 
134
 
    /**
135
 
     * Convenience constructor that creates a default configuration object.
136
 
     * @return Default HTMLPurifier_Config object.
137
 
     */
138
 
    public static function createDefault() {
139
 
        $definition = HTMLPurifier_ConfigSchema::instance();
140
 
        $config = new HTMLPurifier_Config($definition);
141
 
        return $config;
142
 
    }
143
 
 
144
 
    /**
145
 
     * Retreives a value from the configuration.
146
 
     * @param $key String key
147
 
     */
148
 
    public function get($key, $a = null) {
149
 
        if ($a !== null) {
150
 
            $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING);
151
 
            $key = "$key.$a";
152
 
        }
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),
157
 
                E_USER_WARNING);
158
 
            return;
159
 
        }
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,
163
 
                E_USER_ERROR);
164
 
            return;
165
 
        }
166
 
        if ($this->lock) {
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);
170
 
                return;
171
 
            }
172
 
        }
173
 
        return $this->plist->get($key);
174
 
    }
175
 
 
176
 
    /**
177
 
     * Retreives an array of directives to values from a given namespace
178
 
     * @param $namespace String namespace
179
 
     */
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),
185
 
                E_USER_WARNING);
186
 
            return;
187
 
        }
188
 
        return $full[$namespace];
189
 
    }
190
 
 
191
 
    /**
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
195
 
     *       before processing!
196
 
     * @param $namespace Namespace to get serial for
197
 
     */
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));
203
 
        }
204
 
        return $this->serials[$namespace];
205
 
    }
206
 
 
207
 
    /**
208
 
     * Returns a SHA-1 signature for the entire configuration object
209
 
     * that uniquely identifies that particular configuration
210
 
     */
211
 
    public function getSerial() {
212
 
        if (empty($this->serial)) {
213
 
            $this->serial = sha1(serialize($this->getAll()));
214
 
        }
215
 
        return $this->serial;
216
 
    }
217
 
 
218
 
    /**
219
 
     * Retrieves all directives, organized by namespace
220
 
     * @warning This is a pretty inefficient function, avoid if you can
221
 
     */
222
 
    public function getAll() {
223
 
        if (!$this->finalized) $this->autoFinalize();
224
 
        $ret = array();
225
 
        foreach ($this->plist->squash() as $name => $value) {
226
 
            list($ns, $key) = explode('.', $name, 2);
227
 
            $ret[$ns][$key] = $value;
228
 
        }
229
 
        return $ret;
230
 
    }
231
 
 
232
 
    /**
233
 
     * Sets a value to configuration.
234
 
     * @param $key String key
235
 
     * @param $value Mixed value
236
 
     */
237
 
    public function set($key, $value, $a = null) {
238
 
        if (strpos($key, '.') === false) {
239
 
            $namespace = $key;
240
 
            $directive = $value;
241
 
            $value = $a;
242
 
            $key = "$key.$directive";
243
 
            $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
244
 
        } else {
245
 
            list($namespace) = explode('.', $key);
246
 
        }
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',
250
 
                E_USER_WARNING);
251
 
            return;
252
 
        }
253
 
        $def = $this->def->info[$key];
254
 
 
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);
259
 
                return;
260
 
            }
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);
265
 
            return;
266
 
        }
267
 
 
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;
271
 
        if ($rtype < 0) {
272
 
            $type = -$rtype;
273
 
            $allow_null = true;
274
 
        } else {
275
 
            $type = $rtype;
276
 
            $allow_null = isset($def->allow_null);
277
 
        }
278
 
 
279
 
        try {
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);
283
 
            return;
284
 
        }
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];
289
 
            }
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);
294
 
                return;
295
 
            }
296
 
        }
297
 
        $this->plist->set($key, $value);
298
 
 
299
 
        // reset definitions if the directives they depend on changed
300
 
        // this is a very costly process, so it's discouraged
301
 
        // with finalization
302
 
        if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
303
 
            $this->definitions[$namespace] = null;
304
 
        }
305
 
 
306
 
        $this->serials[$namespace] = false;
307
 
    }
308
 
 
309
 
    /**
310
 
     * Convenience function for error reporting
311
 
     */
312
 
    private function _listify($lookup) {
313
 
        $list = array();
314
 
        foreach ($lookup as $name => $b) $list[] = $name;
315
 
        return implode(', ', $list);
316
 
    }
317
 
 
318
 
    /**
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
327
 
     *             named, instead.
328
 
     */
329
 
    public function getHTMLDefinition($raw = false, $optimized = false) {
330
 
        return $this->getDefinition('HTML', $raw, $optimized);
331
 
    }
332
 
 
333
 
    /**
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
342
 
     *             named, instead.
343
 
     */
344
 
    public function getCSSDefinition($raw = false, $optimized = false) {
345
 
        return $this->getDefinition('CSS', $raw, $optimized);
346
 
    }
347
 
 
348
 
    /**
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
357
 
     *             named, instead.
358
 
     */
359
 
    public function getURIDefinition($raw = false, $optimized = false) {
360
 
        return $this->getDefinition('URI', $raw, $optimized);
361
 
    }
362
 
 
363
 
    /**
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."
375
 
     */
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");
379
 
        }
380
 
        if (!$this->finalized) $this->autoFinalize();
381
 
        // temporarily suspend locks, so we can handle recursive definition calls
382
 
        $lock = $this->lock;
383
 
        $this->lock = null;
384
 
        $factory = HTMLPurifier_DefinitionCacheFactory::instance();
385
 
        $cache = $factory->create($type, $this);
386
 
        $this->lock = $lock;
387
 
        if (!$raw) {
388
 
            // full definition
389
 
            // ---------------
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
394
 
                if ($def->setup) {
395
 
                    return $def;
396
 
                } else {
397
 
                    $def->setup($this);
398
 
                    if ($def->optimized) $cache->add($def, $this);
399
 
                    return $def;
400
 
                }
401
 
            }
402
 
            // check if definition is in cache
403
 
            $def = $cache->get($this);
404
 
            if ($def) {
405
 
                // definition in cache, save to memory and return it
406
 
                $this->definitions[$type] = $def;
407
 
                return $def;
408
 
            }
409
 
            // initialize it
410
 
            $def = $this->initDefinition($type);
411
 
            // set it up
412
 
            $this->lock = $type;
413
 
            $def->setup($this);
414
 
            $this->lock = null;
415
 
            // save in cache
416
 
            $cache->add($def, $this);
417
 
            // return it
418
 
            return $def;
419
 
        } else {
420
 
            // raw definition
421
 
            // --------------
422
 
            // check preconditions
423
 
            $def = null;
424
 
            if ($optimized) {
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");
428
 
                }
429
 
            }
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);
435
 
                }
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);
439
 
                }
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);
444
 
                }
445
 
            }
446
 
            // check if definition was in memory
447
 
            if ($def) {
448
 
                if ($def->setup) {
449
 
                    // invariant: $optimized === true (checked above)
450
 
                    return null;
451
 
                } else {
452
 
                    return $def;
453
 
                }
454
 
            }
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.)
460
 
            if ($optimized) {
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);
465
 
                if ($def) {
466
 
                    // save the full definition for later, but don't
467
 
                    // return it yet
468
 
                    $this->definitions[$type] = $def;
469
 
                    return null;
470
 
                }
471
 
            }
472
 
            // check invariants for creation
473
 
            if (!$optimized) {
474
 
                if (!is_null($this->get($type . '.DefinitionID'))) {
475
 
                    if ($this->chatty) {
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);
477
 
                    } else {
478
 
                        $this->triggerError("Useless DefinitionID declaration", E_USER_WARNING);
479
 
                    }
480
 
                }
481
 
            }
482
 
            // initialize it
483
 
            $def = $this->initDefinition($type);
484
 
            $def->optimized = $optimized;
485
 
            return $def;
486
 
        }
487
 
        throw new HTMLPurifier_Exception("The impossible happened!");
488
 
    }
489
 
 
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();
498
 
        } else {
499
 
            throw new HTMLPurifier_Exception("Definition of $type type not supported");
500
 
        }
501
 
        $this->definitions[$type] = $def;
502
 
        return $def;
503
 
    }
504
 
 
505
 
    public function maybeGetRawDefinition($name) {
506
 
        return $this->getDefinition($name, true, true);
507
 
    }
508
 
 
509
 
    public function maybeGetRawHTMLDefinition() {
510
 
        return $this->getDefinition('HTML', true, true);
511
 
    }
512
 
 
513
 
    public function maybeGetRawCSSDefinition() {
514
 
        return $this->getDefinition('CSS', true, true);
515
 
    }
516
 
 
517
 
    public function maybeGetRawURIDefinition() {
518
 
        return $this->getDefinition('URI', true, true);
519
 
    }
520
 
 
521
 
    /**
522
 
     * Loads configuration values from an array with the following structure:
523
 
     * Namespace.Directive => Value
524
 
     * @param $config_array Configuration associative array
525
 
     */
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);
532
 
            } else {
533
 
                $namespace = $key;
534
 
                $namespace_values = $value;
535
 
                foreach ($namespace_values as $directive => $value) {
536
 
                    $this->set($namespace .'.'. $directive, $value);
537
 
                }
538
 
            }
539
 
        }
540
 
    }
541
 
 
542
 
    /**
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
547
 
     */
548
 
    public static function getAllowedDirectivesForForm($allowed, $schema = null) {
549
 
        if (!$schema) {
550
 
            $schema = HTMLPurifier_ConfigSchema::instance();
551
 
        }
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) {
559
 
                     // directive
560
 
                     if ($ns_or_directive[0] == '-') {
561
 
                         $blacklisted_directives[substr($ns_or_directive, 1)] = true;
562
 
                     } else {
563
 
                         $allowed_directives[$ns_or_directive] = true;
564
 
                     }
565
 
                 } else {
566
 
                     // namespace
567
 
                     $allowed_ns[$ns_or_directive] = true;
568
 
                 }
569
 
             }
570
 
        }
571
 
        $ret = array();
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;
577
 
            }
578
 
            if (isset($def->isAlias)) continue;
579
 
            if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
580
 
            $ret[] = array($ns, $directive);
581
 
        }
582
 
        return $ret;
583
 
    }
584
 
 
585
 
    /**
586
 
     * Loads configuration values from $_GET/$_POST that were posted
587
 
     * via ConfigForm
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
593
 
     */
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);
597
 
        return $config;
598
 
    }
599
 
 
600
 
    /**
601
 
     * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
602
 
     * @note Same parameters as loadArrayFromForm
603
 
     */
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);
607
 
    }
608
 
 
609
 
    /**
610
 
     * Prepares an array from a form into something usable for the more
611
 
     * strict parts of HTMLPurifier_Config
612
 
     */
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();
616
 
 
617
 
        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
618
 
        $ret = array();
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;
624
 
                continue;
625
 
            }
626
 
            if (!isset($array[$skey])) continue;
627
 
            $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
628
 
            $ret[$ns][$directive] = $value;
629
 
        }
630
 
        return $ret;
631
 
    }
632
 
 
633
 
    /**
634
 
     * Loads configuration values from an ini file
635
 
     * @param $filename Name of ini file
636
 
     */
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);
641
 
    }
642
 
 
643
 
    /**
644
 
     * Checks whether or not the configuration object is finalized.
645
 
     * @param $error String error message, or false for no error
646
 
     */
647
 
    public function isFinalized($error = false) {
648
 
        if ($this->finalized && $error) {
649
 
            $this->triggerError($error, E_USER_ERROR);
650
 
        }
651
 
        return $this->finalized;
652
 
    }
653
 
 
654
 
    /**
655
 
     * Finalizes configuration only if auto finalize is on and not
656
 
     * already finalized
657
 
     */
658
 
    public function autoFinalize() {
659
 
        if ($this->autoFinalize) {
660
 
            $this->finalize();
661
 
        } else {
662
 
            $this->plist->squash(true);
663
 
        }
664
 
    }
665
 
 
666
 
    /**
667
 
     * Finalizes a configuration object, prohibiting further change
668
 
     */
669
 
    public function finalize() {
670
 
        $this->finalized = true;
671
 
        $this->parser = null;
672
 
    }
673
 
 
674
 
    /**
675
 
     * Produces a nicely formatted error message by supplying the
676
 
     * stack frame information OUTSIDE of HTMLPurifier_Config.
677
 
     */
678
 
    protected function triggerError($msg, $no) {
679
 
        // determine previous stack frame
680
 
        $extra = '';
681
 
        if ($this->chatty) {
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') {
687
 
                    continue;
688
 
                }
689
 
                $frame = $trace[$i];
690
 
                $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
691
 
                break;
692
 
            }
693
 
        }
694
 
        trigger_error($msg . $extra, $no);
695
 
    }
696
 
 
697
 
    /**
698
 
     * Returns a serialized form of the configuration object that can
699
 
     * be reconstituted.
700
 
     */
701
 
    public function serialize() {
702
 
        $this->getDefinition('HTML');
703
 
        $this->getDefinition('CSS');
704
 
        $this->getDefinition('URI');
705
 
        return serialize($this);
706
 
    }
707
 
 
708
 
}
709
 
 
710
 
// vim: et sw=4 sts=4
 
1
<?php
 
2
 
 
3
/**
 
4
 * Configuration object that triggers customizable behavior.
 
5
 *
 
6
 * @warning This class is strongly defined: that means that the class
 
7
 *          will fail if an undefined directive is retrieved or set.
 
8
 *
 
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.
 
14
 *
 
15
 * @todo Reconsider some of the public member variables
 
16
 */
 
17
class HTMLPurifier_Config
 
18
{
 
19
 
 
20
    /**
 
21
     * HTML Purifier's version
 
22
     * @type string
 
23
     */
 
24
    public $version = '4.6.0';
 
25
 
 
26
    /**
 
27
     * Whether or not to automatically finalize
 
28
     * the object if a read operation is done.
 
29
     * @type bool
 
30
     */
 
31
    public $autoFinalize = true;
 
32
 
 
33
    // protected member variables
 
34
 
 
35
    /**
 
36
     * Namespace indexed array of serials for specific namespaces.
 
37
     * @see getSerial() for more info.
 
38
     * @type string[]
 
39
     */
 
40
    protected $serials = array();
 
41
 
 
42
    /**
 
43
     * Serial for entire configuration object.
 
44
     * @type string
 
45
     */
 
46
    protected $serial;
 
47
 
 
48
    /**
 
49
     * Parser for variables.
 
50
     * @type HTMLPurifier_VarParser_Flexible
 
51
     */
 
52
    protected $parser = null;
 
53
 
 
54
    /**
 
55
     * Reference HTMLPurifier_ConfigSchema for value checking.
 
56
     * @type HTMLPurifier_ConfigSchema
 
57
     * @note This is public for introspective purposes. Please don't
 
58
     *       abuse!
 
59
     */
 
60
    public $def;
 
61
 
 
62
    /**
 
63
     * Indexed array of definitions.
 
64
     * @type HTMLPurifier_Definition[]
 
65
     */
 
66
    protected $definitions;
 
67
 
 
68
    /**
 
69
     * Whether or not config is finalized.
 
70
     * @type bool
 
71
     */
 
72
    protected $finalized = false;
 
73
 
 
74
    /**
 
75
     * Property list containing configuration directives.
 
76
     * @type array
 
77
     */
 
78
    protected $plist;
 
79
 
 
80
    /**
 
81
     * Whether or not a set is taking place due to an alias lookup.
 
82
     * @type bool
 
83
     */
 
84
    private $aliasMode;
 
85
 
 
86
    /**
 
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
 
89
     * and exceptions.
 
90
     * @type bool
 
91
     */
 
92
    public $chatty = true;
 
93
 
 
94
    /**
 
95
     * Current lock; only gets to this namespace are allowed.
 
96
     * @type string
 
97
     */
 
98
    private $lock;
 
99
 
 
100
    /**
 
101
     * Constructor
 
102
     * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
 
103
     * what directives are allowed.
 
104
     * @param HTMLPurifier_PropertyList $parent
 
105
     */
 
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
 
 
114
    /**
 
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
 
122
     */
 
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) {
 
130
            $ret = HTMLPurifier_Config::createDefault();
 
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
 
 
140
    /**
 
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.
 
144
     */
 
145
    public static function inherit(HTMLPurifier_Config $config)
 
146
    {
 
147
        return new HTMLPurifier_Config($config->def, $config->plist);
 
148
    }
 
149
 
 
150
    /**
 
151
     * Convenience constructor that creates a default configuration object.
 
152
     * @return HTMLPurifier_Config default object.
 
153
     */
 
154
    public static function createDefault()
 
155
    {
 
156
        $definition = HTMLPurifier_ConfigSchema::instance();
 
157
        $config = new HTMLPurifier_Config($definition);
 
158
        return $config;
 
159
    }
 
160
 
 
161
    /**
 
162
     * Retrieves a value from the configuration.
 
163
     *
 
164
     * @param string $key String key
 
165
     * @param mixed $a
 
166
     *
 
167
     * @return mixed
 
168
     */
 
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
 
 
213
    /**
 
214
     * Retrieves an array of directives to values from a given namespace
 
215
     *
 
216
     * @param string $namespace String namespace
 
217
     *
 
218
     * @return array
 
219
     */
 
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
 
 
237
    /**
 
238
     * Returns a SHA-1 signature of a segment of the configuration object
 
239
     * that uniquely identifies that particular configuration
 
240
     *
 
241
     * @param string $namespace Namespace to get serial for
 
242
     *
 
243
     * @return string
 
244
     * @note Revision is handled specially and is removed from the batch
 
245
     *       before processing!
 
246
     */
 
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
 
 
257
    /**
 
258
     * Returns a SHA-1 signature for the entire configuration object
 
259
     * that uniquely identifies that particular configuration
 
260
     *
 
261
     * @return string
 
262
     */
 
263
    public function getSerial()
 
264
    {
 
265
        if (empty($this->serial)) {
 
266
            $this->serial = sha1(serialize($this->getAll()));
 
267
        }
 
268
        return $this->serial;
 
269
    }
 
270
 
 
271
    /**
 
272
     * Retrieves all directives, organized by namespace
 
273
     *
 
274
     * @warning This is a pretty inefficient function, avoid if you can
 
275
     */
 
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
 
 
289
    /**
 
290
     * Sets a value to configuration.
 
291
     *
 
292
     * @param string $key key
 
293
     * @param mixed $value value
 
294
     * @param mixed $a
 
295
     */
 
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 ' .
 
351
                HTMLPurifier_VarParser::getTypeName($type),
 
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
 
 
383
    /**
 
384
     * Convenience function for error reporting
 
385
     *
 
386
     * @param array $lookup
 
387
     *
 
388
     * @return string
 
389
     */
 
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
 
 
399
    /**
 
400
     * Retrieves object reference to the HTML definition.
 
401
     *
 
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
 
409
     *             named, instead.
 
410
     *
 
411
     * @return HTMLPurifier_HTMLDefinition
 
412
     */
 
413
    public function getHTMLDefinition($raw = false, $optimized = false)
 
414
    {
 
415
        return $this->getDefinition('HTML', $raw, $optimized);
 
416
    }
 
417
 
 
418
    /**
 
419
     * Retrieves object reference to the CSS definition
 
420
     *
 
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
 
428
     *             named, instead.
 
429
     *
 
430
     * @return HTMLPurifier_CSSDefinition
 
431
     */
 
432
    public function getCSSDefinition($raw = false, $optimized = false)
 
433
    {
 
434
        return $this->getDefinition('CSS', $raw, $optimized);
 
435
    }
 
436
 
 
437
    /**
 
438
     * Retrieves object reference to the URI definition
 
439
     *
 
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
 
447
     *             named, instead.
 
448
     *
 
449
     * @return HTMLPurifier_URIDefinition
 
450
     */
 
451
    public function getURIDefinition($raw = false, $optimized = false)
 
452
    {
 
453
        return $this->getDefinition('URI', $raw, $optimized);
 
454
    }
 
455
 
 
456
    /**
 
457
     * Retrieves a definition
 
458
     *
 
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."
 
469
     *
 
470
     * @throws HTMLPurifier_Exception
 
471
     * @return HTMLPurifier_Definition
 
472
     */
 
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;
 
484
        $factory = HTMLPurifier_DefinitionCacheFactory::instance();
 
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
 
 
618
    /**
 
619
     * Initialise definition
 
620
     *
 
621
     * @param string $type What type of definition to create
 
622
     *
 
623
     * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
 
624
     * @throws HTMLPurifier_Exception
 
625
     */
 
626
    private function initDefinition($type)
 
627
    {
 
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();
 
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
 
 
664
    /**
 
665
     * Loads configuration values from an array with the following structure:
 
666
     * Namespace.Directive => Value
 
667
     *
 
668
     * @param array $config_array Configuration associative array
 
669
     */
 
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
 
 
689
    /**
 
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.
 
693
     *
 
694
     * @param array $allowed List of allowed namespaces/directives
 
695
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
 
696
     *
 
697
     * @return array
 
698
     */
 
699
    public static function getAllowedDirectivesForForm($allowed, $schema = null)
 
700
    {
 
701
        if (!$schema) {
 
702
            $schema = HTMLPurifier_ConfigSchema::instance();
 
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
 
 
747
    /**
 
748
     * Loads configuration values from $_GET/$_POST that were posted
 
749
     * via ConfigForm
 
750
     *
 
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
 
756
     *
 
757
     * @return mixed
 
758
     */
 
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
 
 
766
    /**
 
767
     * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
 
768
     *
 
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
 
773
     */
 
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
 
 
780
    /**
 
781
     * Prepares an array from a form into something usable for the more
 
782
     * strict parts of HTMLPurifier_Config
 
783
     *
 
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
 
789
     *
 
790
     * @return array
 
791
     */
 
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
 
 
817
    /**
 
818
     * Loads configuration values from an ini file
 
819
     *
 
820
     * @param string $filename Name of ini file
 
821
     */
 
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
 
 
831
    /**
 
832
     * Checks whether or not the configuration object is finalized.
 
833
     *
 
834
     * @param string|bool $error String error message, or false for no error
 
835
     *
 
836
     * @return bool
 
837
     */
 
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
 
 
846
    /**
 
847
     * Finalizes configuration only if auto finalize is on and not
 
848
     * already finalized
 
849
     */
 
850
    public function autoFinalize()
 
851
    {
 
852
        if ($this->autoFinalize) {
 
853
            $this->finalize();
 
854
        } else {
 
855
            $this->plist->squash(true);
 
856
        }
 
857
    }
 
858
 
 
859
    /**
 
860
     * Finalizes a configuration object, prohibiting further change
 
861
     */
 
862
    public function finalize()
 
863
    {
 
864
        $this->finalized = true;
 
865
        $this->parser = null;
 
866
    }
 
867
 
 
868
    /**
 
869
     * Produces a nicely formatted error message by supplying the
 
870
     * stack frame information OUTSIDE of HTMLPurifier_Config.
 
871
     *
 
872
     * @param string $msg An error message
 
873
     * @param int $no An error number
 
874
     */
 
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
 
 
895
    /**
 
896
     * Returns a serialized form of the configuration object that can
 
897
     * be reconstituted.
 
898
     *
 
899
     * @return string
 
900
     */
 
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