Issues (3083)

modules/protector/library/HTMLPurifier/Config.php (1 issue)

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.15.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);
0 ignored issues
show
Documentation Bug introduced by
It seems like new HTMLPurifier_PropertyList($parent) of type HTMLPurifier_PropertyList is incompatible with the declared type array of property $plist.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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|null
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|null
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|null
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|null
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
    /**
650
     * @return HTMLPurifier_HTMLDefinition|null
651
     */
652
    public function maybeGetRawHTMLDefinition()
653
    {
654
        return $this->getDefinition('HTML', true, true);
655
    }
656
    
657
    /**
658
     * @return HTMLPurifier_CSSDefinition|null
659
     */
660
    public function maybeGetRawCSSDefinition()
661
    {
662
        return $this->getDefinition('CSS', true, true);
663
    }
664
    
665
    /**
666
     * @return HTMLPurifier_URIDefinition|null
667
     */
668
    public function maybeGetRawURIDefinition()
669
    {
670
        return $this->getDefinition('URI', true, true);
671
    }
672
673
    /**
674
     * Loads configuration values from an array with the following structure:
675
     * Namespace.Directive => Value
676
     *
677
     * @param array $config_array Configuration associative array
678
     */
679
    public function loadArray($config_array)
680
    {
681
        if ($this->isFinalized('Cannot load directives after finalization')) {
682
            return;
683
        }
684
        foreach ($config_array as $key => $value) {
685
            $key = str_replace('_', '.', $key);
686
            if (strpos($key, '.') !== false) {
687
                $this->set($key, $value);
688
            } else {
689
                $namespace = $key;
690
                $namespace_values = $value;
691
                foreach ($namespace_values as $directive => $value2) {
692
                    $this->set($namespace .'.'. $directive, $value2);
693
                }
694
            }
695
        }
696
    }
697
698
    /**
699
     * Returns a list of array(namespace, directive) for all directives
700
     * that are allowed in a web-form context as per an allowed
701
     * namespaces/directives list.
702
     *
703
     * @param array $allowed List of allowed namespaces/directives
704
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
705
     *
706
     * @return array
707
     */
708
    public static function getAllowedDirectivesForForm($allowed, $schema = null)
709
    {
710
        if (!$schema) {
711
            $schema = HTMLPurifier_ConfigSchema::instance();
712
        }
713
        if ($allowed !== true) {
714
            if (is_string($allowed)) {
715
                $allowed = array($allowed);
716
            }
717
            $allowed_ns = array();
718
            $allowed_directives = array();
719
            $blacklisted_directives = array();
720
            foreach ($allowed as $ns_or_directive) {
721
                if (strpos($ns_or_directive, '.') !== false) {
722
                    // directive
723
                    if ($ns_or_directive[0] == '-') {
724
                        $blacklisted_directives[substr($ns_or_directive, 1)] = true;
725
                    } else {
726
                        $allowed_directives[$ns_or_directive] = true;
727
                    }
728
                } else {
729
                    // namespace
730
                    $allowed_ns[$ns_or_directive] = true;
731
                }
732
            }
733
        }
734
        $ret = array();
735
        foreach ($schema->info as $key => $def) {
736
            list($ns, $directive) = explode('.', $key, 2);
737
            if ($allowed !== true) {
738
                if (isset($blacklisted_directives["$ns.$directive"])) {
739
                    continue;
740
                }
741
                if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
742
                    continue;
743
                }
744
            }
745
            if (isset($def->isAlias)) {
746
                continue;
747
            }
748
            if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
749
                continue;
750
            }
751
            $ret[] = array($ns, $directive);
752
        }
753
        return $ret;
754
    }
755
756
    /**
757
     * Loads configuration values from $_GET/$_POST that were posted
758
     * via ConfigForm
759
     *
760
     * @param array $array $_GET or $_POST array to import
761
     * @param string|bool $index Index/name that the config variables are in
762
     * @param array|bool $allowed List of allowed namespaces/directives
763
     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
764
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
765
     *
766
     * @return mixed
767
     */
768
    public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
769
    {
770
        $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
771
        $config = HTMLPurifier_Config::create($ret, $schema);
772
        return $config;
773
    }
774
775
    /**
776
     * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
777
     *
778
     * @param array $array $_GET or $_POST array to import
779
     * @param string|bool $index Index/name that the config variables are in
780
     * @param array|bool $allowed List of allowed namespaces/directives
781
     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
782
     */
783
    public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
784
    {
785
         $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
786
         $this->loadArray($ret);
787
    }
788
789
    /**
790
     * Prepares an array from a form into something usable for the more
791
     * strict parts of HTMLPurifier_Config
792
     *
793
     * @param array $array $_GET or $_POST array to import
794
     * @param string|bool $index Index/name that the config variables are in
795
     * @param array|bool $allowed List of allowed namespaces/directives
796
     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
797
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
798
     *
799
     * @return array
800
     */
801
    public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
802
    {
803
        if ($index !== false) {
804
            $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
805
        }
806
        $mq = $mq_fix && version_compare(PHP_VERSION, '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
807
808
        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
809
        $ret = array();
810
        foreach ($allowed as $key) {
811
            list($ns, $directive) = $key;
812
            $skey = "$ns.$directive";
813
            if (!empty($array["Null_$skey"])) {
814
                $ret[$ns][$directive] = null;
815
                continue;
816
            }
817
            if (!isset($array[$skey])) {
818
                continue;
819
            }
820
            $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
821
            $ret[$ns][$directive] = $value;
822
        }
823
        return $ret;
824
    }
825
826
    /**
827
     * Loads configuration values from an ini file
828
     *
829
     * @param string $filename Name of ini file
830
     */
831
    public function loadIni($filename)
832
    {
833
        if ($this->isFinalized('Cannot load directives after finalization')) {
834
            return;
835
        }
836
        $array = parse_ini_file($filename, true);
837
        $this->loadArray($array);
838
    }
839
840
    /**
841
     * Checks whether or not the configuration object is finalized.
842
     *
843
     * @param string|bool $error String error message, or false for no error
844
     *
845
     * @return bool
846
     */
847
    public function isFinalized($error = false)
848
    {
849
        if ($this->finalized && $error) {
850
            $this->triggerError($error, E_USER_ERROR);
851
        }
852
        return $this->finalized;
853
    }
854
855
    /**
856
     * Finalizes configuration only if auto finalize is on and not
857
     * already finalized
858
     */
859
    public function autoFinalize()
860
    {
861
        if ($this->autoFinalize) {
862
            $this->finalize();
863
        } else {
864
            $this->plist->squash(true);
865
        }
866
    }
867
868
    /**
869
     * Finalizes a configuration object, prohibiting further change
870
     */
871
    public function finalize()
872
    {
873
        $this->finalized = true;
874
        $this->parser = null;
875
    }
876
877
    /**
878
     * Produces a nicely formatted error message by supplying the
879
     * stack frame information OUTSIDE of HTMLPurifier_Config.
880
     *
881
     * @param string $msg An error message
882
     * @param int $no An error number
883
     */
884
    protected function triggerError($msg, $no)
885
    {
886
        // determine previous stack frame
887
        $extra = '';
888
        if ($this->chatty) {
889
            $trace = debug_backtrace();
890
            // zip(tail(trace), trace) -- but PHP is not Haskell har har
891
            for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
892
                // XXX this is not correct on some versions of HTML Purifier
893
                if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
894
                    continue;
895
                }
896
                $frame = $trace[$i];
897
                $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
898
                break;
899
            }
900
        }
901
        trigger_error($msg . $extra, $no);
902
    }
903
904
    /**
905
     * Returns a serialized form of the configuration object that can
906
     * be reconstituted.
907
     *
908
     * @return string
909
     */
910
    public function serialize()
911
    {
912
        $this->getDefinition('HTML');
913
        $this->getDefinition('CSS');
914
        $this->getDefinition('URI');
915
        return serialize($this);
916
    }
917
918
}
919
920
// vim: et sw=4 sts=4
921