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 */
17class HTMLPurifier_Config
18{
19
20    /**
21     * HTML Purifier's version
22     * @type string
23     */
24    public $version = '4.12.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    /**
650     * @return HTMLPurifier_HTMLDefinition
651     */
652    public function maybeGetRawHTMLDefinition()
653    {
654        return $this->getDefinition('HTML', true, true);
655    }
656
657    /**
658     * @return HTMLPurifier_CSSDefinition
659     */
660    public function maybeGetRawCSSDefinition()
661    {
662        return $this->getDefinition('CSS', true, true);
663    }
664
665    /**
666     * @return HTMLPurifier_URIDefinition
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 && 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