1<?php
2
3/**
4 * Defines allowed CSS attributes and what their values are.
5 * @see HTMLPurifier_HTMLDefinition
6 */
7class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
8{
9
10    public $type = 'CSS';
11
12    /**
13     * Assoc array of attribute name to definition object.
14     * @type HTMLPurifier_AttrDef[]
15     */
16    public $info = array();
17
18    /**
19     * Constructs the info array.  The meat of this class.
20     * @param HTMLPurifier_Config $config
21     */
22    protected function doSetup($config)
23    {
24        $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
25            array('left', 'right', 'center', 'justify'),
26            false
27        );
28
29        $border_style =
30            $this->info['border-bottom-style'] =
31            $this->info['border-right-style'] =
32            $this->info['border-left-style'] =
33            $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
34                array(
35                    'none',
36                    'hidden',
37                    'dotted',
38                    'dashed',
39                    'solid',
40                    'double',
41                    'groove',
42                    'ridge',
43                    'inset',
44                    'outset'
45                ),
46                false
47            );
48
49        $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
50
51        $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
52            array('none', 'left', 'right', 'both'),
53            false
54        );
55        $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
56            array('none', 'left', 'right'),
57            false
58        );
59        $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
60            array('normal', 'italic', 'oblique'),
61            false
62        );
63        $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
64            array('normal', 'small-caps'),
65            false
66        );
67
68        $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
69            array(
70                new HTMLPurifier_AttrDef_Enum(array('none')),
71                new HTMLPurifier_AttrDef_CSS_URI()
72            )
73        );
74
75        $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
76            array('inside', 'outside'),
77            false
78        );
79        $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
80            array(
81                'disc',
82                'circle',
83                'square',
84                'decimal',
85                'lower-roman',
86                'upper-roman',
87                'lower-alpha',
88                'upper-alpha',
89                'none'
90            ),
91            false
92        );
93        $this->info['list-style-image'] = $uri_or_none;
94
95        $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
96
97        $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
98            array('capitalize', 'uppercase', 'lowercase', 'none'),
99            false
100        );
101        $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
102
103        $this->info['background-image'] = $uri_or_none;
104        $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
105            array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
106        );
107        $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
108            array('scroll', 'fixed')
109        );
110        $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
111
112        $border_color =
113            $this->info['border-top-color'] =
114            $this->info['border-bottom-color'] =
115            $this->info['border-left-color'] =
116            $this->info['border-right-color'] =
117            $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
118                array(
119                    new HTMLPurifier_AttrDef_Enum(array('transparent')),
120                    new HTMLPurifier_AttrDef_CSS_Color()
121                )
122            );
123
124        $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
125
126        $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
127
128        $border_width =
129            $this->info['border-top-width'] =
130            $this->info['border-bottom-width'] =
131            $this->info['border-left-width'] =
132            $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
133                array(
134                    new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
135                    new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
136                )
137            );
138
139        $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
140
141        $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
142            array(
143                new HTMLPurifier_AttrDef_Enum(array('normal')),
144                new HTMLPurifier_AttrDef_CSS_Length()
145            )
146        );
147
148        $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
149            array(
150                new HTMLPurifier_AttrDef_Enum(array('normal')),
151                new HTMLPurifier_AttrDef_CSS_Length()
152            )
153        );
154
155        $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
156            array(
157                new HTMLPurifier_AttrDef_Enum(
158                    array(
159                        'xx-small',
160                        'x-small',
161                        'small',
162                        'medium',
163                        'large',
164                        'x-large',
165                        'xx-large',
166                        'larger',
167                        'smaller'
168                    )
169                ),
170                new HTMLPurifier_AttrDef_CSS_Percentage(),
171                new HTMLPurifier_AttrDef_CSS_Length()
172            )
173        );
174
175        $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
176            array(
177                new HTMLPurifier_AttrDef_Enum(array('normal')),
178                new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
179                new HTMLPurifier_AttrDef_CSS_Length('0'),
180                new HTMLPurifier_AttrDef_CSS_Percentage(true)
181            )
182        );
183
184        $margin =
185            $this->info['margin-top'] =
186            $this->info['margin-bottom'] =
187            $this->info['margin-left'] =
188            $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
189                array(
190                    new HTMLPurifier_AttrDef_CSS_Length(),
191                    new HTMLPurifier_AttrDef_CSS_Percentage(),
192                    new HTMLPurifier_AttrDef_Enum(array('auto'))
193                )
194            );
195
196        $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
197
198        // non-negative
199        $padding =
200            $this->info['padding-top'] =
201            $this->info['padding-bottom'] =
202            $this->info['padding-left'] =
203            $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
204                array(
205                    new HTMLPurifier_AttrDef_CSS_Length('0'),
206                    new HTMLPurifier_AttrDef_CSS_Percentage(true)
207                )
208            );
209
210        $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
211
212        $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
213            array(
214                new HTMLPurifier_AttrDef_CSS_Length(),
215                new HTMLPurifier_AttrDef_CSS_Percentage()
216            )
217        );
218
219        $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
220            array(
221                new HTMLPurifier_AttrDef_CSS_Length('0'),
222                new HTMLPurifier_AttrDef_CSS_Percentage(true),
223                new HTMLPurifier_AttrDef_Enum(array('auto'))
224            )
225        );
226        $max = $config->get('CSS.MaxImgLength');
227
228        $this->info['min-width'] =
229        $this->info['max-width'] =
230        $this->info['min-height'] =
231        $this->info['max-height'] =
232        $this->info['width'] =
233        $this->info['height'] =
234            $max === null ?
235                $trusted_wh :
236                new HTMLPurifier_AttrDef_Switch(
237                    'img',
238                    // For img tags:
239                    new HTMLPurifier_AttrDef_CSS_Composite(
240                        array(
241                            new HTMLPurifier_AttrDef_CSS_Length('0', $max),
242                            new HTMLPurifier_AttrDef_Enum(array('auto'))
243                        )
244                    ),
245                    // For everyone else:
246                    $trusted_wh
247                );
248
249        $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
250
251        $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
252
253        // this could use specialized code
254        $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
255            array(
256                'normal',
257                'bold',
258                'bolder',
259                'lighter',
260                '100',
261                '200',
262                '300',
263                '400',
264                '500',
265                '600',
266                '700',
267                '800',
268                '900'
269            ),
270            false
271        );
272
273        // MUST be called after other font properties, as it references
274        // a CSSDefinition object
275        $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
276
277        // same here
278        $this->info['border'] =
279        $this->info['border-bottom'] =
280        $this->info['border-top'] =
281        $this->info['border-left'] =
282        $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
283
284        $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
285            array('collapse', 'separate')
286        );
287
288        $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
289            array('top', 'bottom')
290        );
291
292        $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
293            array('auto', 'fixed')
294        );
295
296        $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
297            array(
298                new HTMLPurifier_AttrDef_Enum(
299                    array(
300                        'baseline',
301                        'sub',
302                        'super',
303                        'top',
304                        'text-top',
305                        'middle',
306                        'bottom',
307                        'text-bottom'
308                    )
309                ),
310                new HTMLPurifier_AttrDef_CSS_Length(),
311                new HTMLPurifier_AttrDef_CSS_Percentage()
312            )
313        );
314
315        $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
316
317        // These CSS properties don't work on many browsers, but we live
318        // in THE FUTURE!
319        $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
320            array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
321        );
322
323        if ($config->get('CSS.Proprietary')) {
324            $this->doSetupProprietary($config);
325        }
326
327        if ($config->get('CSS.AllowTricky')) {
328            $this->doSetupTricky($config);
329        }
330
331        if ($config->get('CSS.Trusted')) {
332            $this->doSetupTrusted($config);
333        }
334
335        $allow_important = $config->get('CSS.AllowImportant');
336        // wrap all attr-defs with decorator that handles !important
337        foreach ($this->info as $k => $v) {
338            $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
339        }
340
341        $this->setupConfigStuff($config);
342    }
343
344    /**
345     * @param HTMLPurifier_Config $config
346     */
347    protected function doSetupProprietary($config)
348    {
349        // Internet Explorer only scrollbar colors
350        $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
351        $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
352        $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
353        $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
354        $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
355        $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
356
357        // vendor specific prefixes of opacity
358        $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
359        $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
360
361        // only opacity, for now
362        $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
363
364        // more CSS3
365        $this->info['page-break-after'] =
366        $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
367            array(
368                'auto',
369                'always',
370                'avoid',
371                'left',
372                'right'
373            )
374        );
375        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
376
377        $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
378            array(
379                new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
380                new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
381            ));
382
383        $this->info['border-top-left-radius'] =
384        $this->info['border-top-right-radius'] =
385        $this->info['border-bottom-right-radius'] =
386        $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
387        // TODO: support SLASH syntax
388        $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
389
390    }
391
392    /**
393     * @param HTMLPurifier_Config $config
394     */
395    protected function doSetupTricky($config)
396    {
397        $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
398            array(
399                'inline',
400                'block',
401                'list-item',
402                'run-in',
403                'compact',
404                'marker',
405                'table',
406                'inline-block',
407                'inline-table',
408                'table-row-group',
409                'table-header-group',
410                'table-footer-group',
411                'table-row',
412                'table-column-group',
413                'table-column',
414                'table-cell',
415                'table-caption',
416                'none'
417            )
418        );
419        $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
420            array('visible', 'hidden', 'collapse')
421        );
422        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
423        $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
424    }
425
426    /**
427     * @param HTMLPurifier_Config $config
428     */
429    protected function doSetupTrusted($config)
430    {
431        $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
432            array('static', 'relative', 'absolute', 'fixed')
433        );
434        $this->info['top'] =
435        $this->info['left'] =
436        $this->info['right'] =
437        $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
438            array(
439                new HTMLPurifier_AttrDef_CSS_Length(),
440                new HTMLPurifier_AttrDef_CSS_Percentage(),
441                new HTMLPurifier_AttrDef_Enum(array('auto')),
442            )
443        );
444        $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
445            array(
446                new HTMLPurifier_AttrDef_Integer(),
447                new HTMLPurifier_AttrDef_Enum(array('auto')),
448            )
449        );
450    }
451
452    /**
453     * Performs extra config-based processing. Based off of
454     * HTMLPurifier_HTMLDefinition.
455     * @param HTMLPurifier_Config $config
456     * @todo Refactor duplicate elements into common class (probably using
457     *       composition, not inheritance).
458     */
459    protected function setupConfigStuff($config)
460    {
461        // setup allowed elements
462        $support = "(for information on implementing this, see the " .
463            "support forums) ";
464        $allowed_properties = $config->get('CSS.AllowedProperties');
465        if ($allowed_properties !== null) {
466            foreach ($this->info as $name => $d) {
467                if (!isset($allowed_properties[$name])) {
468                    unset($this->info[$name]);
469                }
470                unset($allowed_properties[$name]);
471            }
472            // emit errors
473            foreach ($allowed_properties as $name => $d) {
474                // :TODO: Is this htmlspecialchars() call really necessary?
475                $name = htmlspecialchars($name);
476                trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
477            }
478        }
479
480        $forbidden_properties = $config->get('CSS.ForbiddenProperties');
481        if ($forbidden_properties !== null) {
482            foreach ($this->info as $name => $d) {
483                if (isset($forbidden_properties[$name])) {
484                    unset($this->info[$name]);
485                }
486            }
487        }
488    }
489}
490
491// vim: et sw=4 sts=4
492