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', 'initial', 'inherit'))
224            )
225        );
226        $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite(
227            array(
228                new HTMLPurifier_AttrDef_CSS_Length('0'),
229                new HTMLPurifier_AttrDef_CSS_Percentage(true),
230                new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
231            )
232        );
233        $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite(
234            array(
235                new HTMLPurifier_AttrDef_CSS_Length('0'),
236                new HTMLPurifier_AttrDef_CSS_Percentage(true),
237                new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
238            )
239        );
240        $max = $config->get('CSS.MaxImgLength');
241
242        $this->info['width'] =
243        $this->info['height'] =
244            $max === null ?
245                $trusted_wh :
246                new HTMLPurifier_AttrDef_Switch(
247                    'img',
248                    // For img tags:
249                    new HTMLPurifier_AttrDef_CSS_Composite(
250                        array(
251                            new HTMLPurifier_AttrDef_CSS_Length('0', $max),
252                            new HTMLPurifier_AttrDef_Enum(array('auto'))
253                        )
254                    ),
255                    // For everyone else:
256                    $trusted_wh
257                );
258        $this->info['min-width'] =
259        $this->info['min-height'] =
260            $max === null ?
261                $trusted_min_wh :
262                new HTMLPurifier_AttrDef_Switch(
263                    'img',
264                    // For img tags:
265                    new HTMLPurifier_AttrDef_CSS_Composite(
266                        array(
267                            new HTMLPurifier_AttrDef_CSS_Length('0', $max),
268                            new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
269                        )
270                    ),
271                    // For everyone else:
272                    $trusted_min_wh
273                );
274        $this->info['max-width'] =
275        $this->info['max-height'] =
276            $max === null ?
277                $trusted_max_wh :
278                new HTMLPurifier_AttrDef_Switch(
279                    'img',
280                    // For img tags:
281                    new HTMLPurifier_AttrDef_CSS_Composite(
282                        array(
283                            new HTMLPurifier_AttrDef_CSS_Length('0', $max),
284                            new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
285                        )
286                    ),
287                    // For everyone else:
288                    $trusted_max_wh
289                );
290
291        $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
292
293        $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
294
295        // this could use specialized code
296        $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
297            array(
298                'normal',
299                'bold',
300                'bolder',
301                'lighter',
302                '100',
303                '200',
304                '300',
305                '400',
306                '500',
307                '600',
308                '700',
309                '800',
310                '900'
311            ),
312            false
313        );
314
315        // MUST be called after other font properties, as it references
316        // a CSSDefinition object
317        $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
318
319        // same here
320        $this->info['border'] =
321        $this->info['border-bottom'] =
322        $this->info['border-top'] =
323        $this->info['border-left'] =
324        $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
325
326        $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
327            array('collapse', 'separate')
328        );
329
330        $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
331            array('top', 'bottom')
332        );
333
334        $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
335            array('auto', 'fixed')
336        );
337
338        $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
339            array(
340                new HTMLPurifier_AttrDef_Enum(
341                    array(
342                        'baseline',
343                        'sub',
344                        'super',
345                        'top',
346                        'text-top',
347                        'middle',
348                        'bottom',
349                        'text-bottom'
350                    )
351                ),
352                new HTMLPurifier_AttrDef_CSS_Length(),
353                new HTMLPurifier_AttrDef_CSS_Percentage()
354            )
355        );
356
357        $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
358
359        // These CSS properties don't work on many browsers, but we live
360        // in THE FUTURE!
361        $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
362            array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
363        );
364
365        if ($config->get('CSS.Proprietary')) {
366            $this->doSetupProprietary($config);
367        }
368
369        if ($config->get('CSS.AllowTricky')) {
370            $this->doSetupTricky($config);
371        }
372
373        if ($config->get('CSS.Trusted')) {
374            $this->doSetupTrusted($config);
375        }
376
377        $allow_important = $config->get('CSS.AllowImportant');
378        // wrap all attr-defs with decorator that handles !important
379        foreach ($this->info as $k => $v) {
380            $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
381        }
382
383        $this->setupConfigStuff($config);
384    }
385
386    /**
387     * @param HTMLPurifier_Config $config
388     */
389    protected function doSetupProprietary($config)
390    {
391        // Internet Explorer only scrollbar colors
392        $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
393        $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
394        $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
395        $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
396        $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
397        $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
398
399        // vendor specific prefixes of opacity
400        $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
401        $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
402
403        // only opacity, for now
404        $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
405
406        // more CSS3
407        $this->info['page-break-after'] =
408        $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
409            array(
410                'auto',
411                'always',
412                'avoid',
413                'left',
414                'right'
415            )
416        );
417        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
418
419        $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
420            array(
421                new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
422                new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
423            ));
424
425        $this->info['border-top-left-radius'] =
426        $this->info['border-top-right-radius'] =
427        $this->info['border-bottom-right-radius'] =
428        $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
429        // TODO: support SLASH syntax
430        $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
431
432    }
433
434    /**
435     * @param HTMLPurifier_Config $config
436     */
437    protected function doSetupTricky($config)
438    {
439        $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
440            array(
441                'inline',
442                'block',
443                'list-item',
444                'run-in',
445                'compact',
446                'marker',
447                'table',
448                'inline-block',
449                'inline-table',
450                'table-row-group',
451                'table-header-group',
452                'table-footer-group',
453                'table-row',
454                'table-column-group',
455                'table-column',
456                'table-cell',
457                'table-caption',
458                'none'
459            )
460        );
461        $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
462            array('visible', 'hidden', 'collapse')
463        );
464        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
465        $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
466    }
467
468    /**
469     * @param HTMLPurifier_Config $config
470     */
471    protected function doSetupTrusted($config)
472    {
473        $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
474            array('static', 'relative', 'absolute', 'fixed')
475        );
476        $this->info['top'] =
477        $this->info['left'] =
478        $this->info['right'] =
479        $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
480            array(
481                new HTMLPurifier_AttrDef_CSS_Length(),
482                new HTMLPurifier_AttrDef_CSS_Percentage(),
483                new HTMLPurifier_AttrDef_Enum(array('auto')),
484            )
485        );
486        $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
487            array(
488                new HTMLPurifier_AttrDef_Integer(),
489                new HTMLPurifier_AttrDef_Enum(array('auto')),
490            )
491        );
492    }
493
494    /**
495     * Performs extra config-based processing. Based off of
496     * HTMLPurifier_HTMLDefinition.
497     * @param HTMLPurifier_Config $config
498     * @todo Refactor duplicate elements into common class (probably using
499     *       composition, not inheritance).
500     */
501    protected function setupConfigStuff($config)
502    {
503        // setup allowed elements
504        $support = "(for information on implementing this, see the " .
505            "support forums) ";
506        $allowed_properties = $config->get('CSS.AllowedProperties');
507        if ($allowed_properties !== null) {
508            foreach ($this->info as $name => $d) {
509                if (!isset($allowed_properties[$name])) {
510                    unset($this->info[$name]);
511                }
512                unset($allowed_properties[$name]);
513            }
514            // emit errors
515            foreach ($allowed_properties as $name => $d) {
516                // :TODO: Is this htmlspecialchars() call really necessary?
517                $name = htmlspecialchars($name);
518                trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
519            }
520        }
521
522        $forbidden_properties = $config->get('CSS.ForbiddenProperties');
523        if ($forbidden_properties !== null) {
524            foreach ($this->info as $name => $d) {
525                if (isset($forbidden_properties[$name])) {
526                    unset($this->info[$name]);
527                }
528            }
529        }
530    }
531}
532
533// vim: et sw=4 sts=4
534