Completed
Push — master ( eb6d5e...9d1a7a )
by Christian
10:30
created

HTMLPurifier_Config   D

Complexity

Total Complexity 128

Size/Duplication

Total Lines 893
Duplicated Lines 5.04 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 45
loc 893
rs 4.4444
c 0
b 0
f 0
wmc 128
lcom 1
cbo 10

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like HTMLPurifier_Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HTMLPurifier_Config, and based on these observations, apply Extract Interface, too.

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
912