Add notes on redefinition, what to do next?
[htmlpurifier.git] / library / HTMLPurifier / ConfigDef.php
blob7f80cc5fed5636fde4cafdbb76ae0fe8a37bf7c2
1 <?php
3 /**
4 * Configuration definition, defines directives and their defaults.
5 * @todo Build documentation generation capabilities.
6 * @todo The ability to define things multiple times is confusing and should
7 * be factored out to its own function named registerDependency() or
8 * addNote(), where only the namespace.name and an extra descriptions
9 * documenting the nature of the dependency are needed. Since it's
10 * possible that the dependency is registered before the configuration
11 * is defined, deferring it to some sort of cache until it actually
12 * gets defined would be wise, keeping it opaque until it does get
13 * defined. We could add a finalize() method which would cause it to
14 * error out if we get a dangling dependency. It's difficult, however,
15 * to know whether or not it's a dependency, or a codependency, that is
16 * neither of them fully depends on it. Where does the configuration go
17 * then? This could be partially resolved by allowing blanket definitions
18 * and then splitting them up into finer-grained versions, however, there
19 * might be implementation difficulties in ini files regarding order of
20 * execution.
22 class HTMLPurifier_ConfigDef {
24 /**
25 * Defaults of the directives and namespaces.
26 * @note This shares the exact same structure as HTMLPurifier_Config::$conf
28 var $defaults = array();
30 /**
31 * Definition of the directives.
33 var $info = array();
35 /**
36 * Definition of namespaces.
38 var $info_namespace = array();
40 /**
41 * Lookup table of allowed types.
43 var $types = array(
44 'string' => true,
45 'istring' => true,
46 'int' => true,
47 'float' => true,
48 'bool' => true,
49 'lookup' => true,
50 'list' => true,
51 'hash' => true,
52 'mixed' => true
55 /**
56 * Initializes the default namespaces.
58 function initialize() {
59 $this->defineNamespace('Core', 'Core features that are always available.');
60 $this->defineNamespace('Attr', 'Features regarding attribute validation.');
61 $this->defineNamespace('URI', 'Features regarding Uniform Resource Identifiers.');
62 $this->defineNamespace('HTML', 'Configuration regarding allowed HTML.');
63 $this->defineNamespace('CSS', 'Configuration regarding allowed CSS.');
64 $this->defineNamespace('Test', 'Testing configuration for our unit tests.');
67 /**
68 * Retrieves an instance of the application-wide configuration definition.
70 function &instance($prototype = null) {
71 static $instance;
72 if ($prototype !== null) {
73 $instance = $prototype;
74 } elseif ($instance === null || $prototype === true) {
75 $instance = new HTMLPurifier_ConfigDef();
76 $instance->initialize();
78 return $instance;
81 /**
82 * Defines a directive for configuration
83 * @warning Will fail of directive's namespace is defined
84 * @todo Collect information on description and allow redefinition
85 * so that multiple files can register a dependency on a
86 * configuration directive.
87 * @param $namespace Namespace the directive is in
88 * @param $name Key of directive
89 * @param $default Default value of directive
90 * @param $type Allowed type of the directive. See
91 * HTMLPurifier_DirectiveDef::$type for allowed values
92 * @param $description Description of directive for documentation
94 function define(
95 $namespace, $name, $default, $type,
96 $description
97 ) {
98 $def =& HTMLPurifier_ConfigDef::instance();
99 if (!isset($def->info[$namespace])) {
100 trigger_error('Cannot define directive for undefined namespace',
101 E_USER_ERROR);
102 return;
104 if (!ctype_alnum($name)) {
105 trigger_error('Directive name must be alphanumeric',
106 E_USER_ERROR);
107 return;
109 if (isset($def->info[$namespace][$name])) {
110 if (
111 $def->info[$namespace][$name]->type !== $type ||
112 $def->defaults[$namespace][$name] !== $default
114 trigger_error('Inconsistent default or type, cannot redefine');
115 return;
117 } else {
118 if (!isset($def->types[$type])) {
119 trigger_error('Invalid type for configuration directive',
120 E_USER_ERROR);
121 return;
123 if ($def->validate($default, $type) === null) {
124 trigger_error('Default value does not match directive type',
125 E_USER_ERROR);
126 return;
128 $def->info[$namespace][$name] =
129 new HTMLPurifier_ConfigEntity_Directive();
130 $def->info[$namespace][$name]->type = $type;
131 $def->defaults[$namespace][$name] = $default;
133 $backtrace = debug_backtrace();
134 $file = $def->mungeFilename($backtrace[0]['file']);
135 $line = $backtrace[0]['line'];
136 $def->info[$namespace][$name]->addDescription($file,$line,$description);
140 * Defines a namespace for directives to be put into.
141 * @param $namespace Namespace's name
142 * @param $description Description of the namespace
144 function defineNamespace($namespace, $description) {
145 $def =& HTMLPurifier_ConfigDef::instance();
146 if (isset($def->info[$namespace])) {
147 trigger_error('Cannot redefine namespace', E_USER_ERROR);
148 return;
150 if (!ctype_alnum($namespace)) {
151 trigger_error('Namespace name must be alphanumeric',
152 E_USER_ERROR);
153 return;
155 $def->info[$namespace] = array();
156 $def->info_namespace[$namespace] = new HTMLPurifier_ConfigEntity_Namespace();
157 $backtrace = debug_backtrace();
158 $file = $def->mungeFilename($backtrace[0]['file']);
159 $line = $backtrace[0]['line'];
160 $def->info_namespace[$namespace]->addDescription($file,$line,$description);
161 $def->defaults[$namespace] = array();
165 * Defines a directive value alias.
167 * Directive value aliases are convenient for developers because it lets
168 * them set a directive to several values and get the same result.
169 * @param $namespace Directive's namespace
170 * @param $name Name of Directive
171 * @param $alias Name of aliased value
172 * @param $real Value aliased value will be converted into
174 function defineValueAliases($namespace, $name, $aliases) {
175 $def =& HTMLPurifier_ConfigDef::instance();
176 if (!isset($def->info[$namespace][$name])) {
177 trigger_error('Cannot set value alias for non-existant directive',
178 E_USER_ERROR);
179 return;
181 foreach ($aliases as $alias => $real) {
182 if (!$def->info[$namespace][$name] !== true &&
183 !isset($def->info[$namespace][$name]->allowed[$real])
185 trigger_error('Cannot define alias to value that is not allowed',
186 E_USER_ERROR);
187 return;
189 if (isset($def->info[$namespace][$name]->allowed[$alias])) {
190 trigger_error('Cannot define alias over allowed value',
191 E_USER_ERROR);
192 return;
194 $def->info[$namespace][$name]->aliases[$alias] = $real;
199 * Defines a set of allowed values for a directive.
200 * @param $namespace Namespace of directive
201 * @param $name Name of directive
202 * @param $allowed_values Arraylist of allowed values
204 function defineAllowedValues($namespace, $name, $allowed_values) {
205 $def =& HTMLPurifier_ConfigDef::instance();
206 if (!isset($def->info[$namespace][$name])) {
207 trigger_error('Cannot define allowed values for undefined directive',
208 E_USER_ERROR);
209 return;
211 if ($def->info[$namespace][$name]->allowed === true) {
212 $def->info[$namespace][$name]->allowed = array();
214 foreach ($allowed_values as $value) {
215 $def->info[$namespace][$name]->allowed[$value] = true;
220 * Validate a variable according to type. Return null if invalid.
222 function validate($var, $type) {
223 if (!isset($this->types[$type])) {
224 trigger_error('Invalid type', E_USER_ERROR);
225 return;
227 switch ($type) {
228 case 'mixed':
229 return $var;
230 case 'istring':
231 case 'string':
232 if (!is_string($var)) return;
233 if ($type === 'istring') $var = strtolower($var);
234 return $var;
235 case 'int':
236 if (is_string($var) && ctype_digit($var)) $var = (int) $var;
237 elseif (!is_int($var)) return;
238 return $var;
239 case 'float':
240 if (is_string($var) && is_numeric($var)) $var = (float) $var;
241 elseif (!is_float($var)) return;
242 return $var;
243 case 'bool':
244 if (is_int($var) && ($var === 0 || $var === 1)) {
245 $var = (bool) $var;
246 } elseif (!is_bool($var)) return;
247 return $var;
248 case 'list':
249 case 'hash':
250 case 'lookup':
251 if (!is_array($var)) return;
252 $keys = array_keys($var);
253 if ($keys === array_keys($keys)) {
254 if ($type == 'list') return $var;
255 elseif ($type == 'lookup') {
256 $new = array();
257 foreach ($var as $key) {
258 $new[$key] = true;
260 return $new;
261 } else return;
263 if ($type === 'lookup') {
264 foreach ($var as $key => $value) {
265 $var[$key] = true;
268 return $var;
272 function mungeFilename($filename) {
273 $offset = strrpos($filename, 'HTMLPurifier');
274 $filename = substr($filename, $offset);
275 $filename = str_replace('\\', '/', $filename);
276 return $filename;
282 * Base class for configuration entity
284 class HTMLPurifier_ConfigEntity
287 * Plaintext descriptions of the configuration entity is. Organized by
288 * file and line number, so multiple descriptions are allowed.
290 var $descriptions = array();
293 * Adds a description to the array
295 function addDescription($file, $line, $description) {
296 if (!isset($this->descriptions[$file])) $this->descriptions[$file] = array();
297 $this->descriptions[$file][$line] = $description;
302 * Structure object describing of a namespace
304 class HTMLPurifier_ConfigEntity_Namespace extends HTMLPurifier_ConfigEntity {}
307 * Structure object containing definition of a directive.
308 * @note This structure does not contain default values
310 class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity
314 * Hash of value aliases, i.e. values that are equivalent.
316 var $aliases = array();
319 * Lookup table of allowed values of the element, bool true if all allowed.
321 var $allowed = true;
324 * Allowed type of the directive. Values are:
325 * - string
326 * - istring (case insensitive string)
327 * - int
328 * - float
329 * - bool
330 * - lookup (array of value => true)
331 * - list (regular numbered index array)
332 * - hash (array of key => value)
333 * - mixed (anything goes)
335 var $type = 'mixed';