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        $this->info['background-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
113            array(
114                new HTMLPurifier_AttrDef_Enum(
115                    array(
116                        'auto',
117                        'cover',
118                        'contain',
119                        'initial',
120                        'inherit',
121                    )
122                ),
123                new HTMLPurifier_AttrDef_CSS_Percentage(),
124                new HTMLPurifier_AttrDef_CSS_Length()
125            )
126        );
127
128        $border_color =
129            $this->info['border-top-color'] =
130            $this->info['border-bottom-color'] =
131            $this->info['border-left-color'] =
132            $this->info['border-right-color'] =
133            $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
134                array(
135                    new HTMLPurifier_AttrDef_Enum(array('transparent')),
136                    new HTMLPurifier_AttrDef_CSS_Color()
137                )
138            );
139
140        $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
141
142        $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
143
144        $border_width =
145            $this->info['border-top-width'] =
146            $this->info['border-bottom-width'] =
147            $this->info['border-left-width'] =
148            $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
149                array(
150                    new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
151                    new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
152                )
153            );
154
155        $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
156
157        $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
158            array(
159                new HTMLPurifier_AttrDef_Enum(array('normal')),
160                new HTMLPurifier_AttrDef_CSS_Length()
161            )
162        );
163
164        $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
165            array(
166                new HTMLPurifier_AttrDef_Enum(array('normal')),
167                new HTMLPurifier_AttrDef_CSS_Length()
168            )
169        );
170
171        $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
172            array(
173                new HTMLPurifier_AttrDef_Enum(
174                    array(
175                        'xx-small',
176                        'x-small',
177                        'small',
178                        'medium',
179                        'large',
180                        'x-large',
181                        'xx-large',
182                        'larger',
183                        'smaller'
184                    )
185                ),
186                new HTMLPurifier_AttrDef_CSS_Percentage(),
187                new HTMLPurifier_AttrDef_CSS_Length()
188            )
189        );
190
191        $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
192            array(
193                new HTMLPurifier_AttrDef_Enum(array('normal')),
194                new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
195                new HTMLPurifier_AttrDef_CSS_Length('0'),
196                new HTMLPurifier_AttrDef_CSS_Percentage(true)
197            )
198        );
199
200        $margin =
201            $this->info['margin-top'] =
202            $this->info['margin-bottom'] =
203            $this->info['margin-left'] =
204            $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
205                array(
206                    new HTMLPurifier_AttrDef_CSS_Length(),
207                    new HTMLPurifier_AttrDef_CSS_Percentage(),
208                    new HTMLPurifier_AttrDef_Enum(array('auto'))
209                )
210            );
211
212        $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
213
214        // non-negative
215        $padding =
216            $this->info['padding-top'] =
217            $this->info['padding-bottom'] =
218            $this->info['padding-left'] =
219            $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
220                array(
221                    new HTMLPurifier_AttrDef_CSS_Length('0'),
222                    new HTMLPurifier_AttrDef_CSS_Percentage(true)
223                )
224            );
225
226        $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
227
228        $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
229            array(
230                new HTMLPurifier_AttrDef_CSS_Length(),
231                new HTMLPurifier_AttrDef_CSS_Percentage()
232            )
233        );
234
235        $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
236            array(
237                new HTMLPurifier_AttrDef_CSS_Length('0'),
238                new HTMLPurifier_AttrDef_CSS_Percentage(true),
239                new HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit'))
240            )
241        );
242        $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite(
243            array(
244                new HTMLPurifier_AttrDef_CSS_Length('0'),
245                new HTMLPurifier_AttrDef_CSS_Percentage(true),
246                new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
247            )
248        );
249        $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite(
250            array(
251                new HTMLPurifier_AttrDef_CSS_Length('0'),
252                new HTMLPurifier_AttrDef_CSS_Percentage(true),
253                new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
254            )
255        );
256        $max = $config->get('CSS.MaxImgLength');
257
258        $this->info['width'] =
259        $this->info['height'] =
260            $max === null ?
261                $trusted_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('auto'))
269                        )
270                    ),
271                    // For everyone else:
272                    $trusted_wh
273                );
274        $this->info['min-width'] =
275        $this->info['min-height'] =
276            $max === null ?
277                $trusted_min_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('initial', 'inherit'))
285                        )
286                    ),
287                    // For everyone else:
288                    $trusted_min_wh
289                );
290        $this->info['max-width'] =
291        $this->info['max-height'] =
292            $max === null ?
293                $trusted_max_wh :
294                new HTMLPurifier_AttrDef_Switch(
295                    'img',
296                    // For img tags:
297                    new HTMLPurifier_AttrDef_CSS_Composite(
298                        array(
299                            new HTMLPurifier_AttrDef_CSS_Length('0', $max),
300                            new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
301                        )
302                    ),
303                    // For everyone else:
304                    $trusted_max_wh
305                );
306
307        $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
308
309        $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
310
311        // this could use specialized code
312        $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
313            array(
314                'normal',
315                'bold',
316                'bolder',
317                'lighter',
318                '100',
319                '200',
320                '300',
321                '400',
322                '500',
323                '600',
324                '700',
325                '800',
326                '900'
327            ),
328            false
329        );
330
331        // MUST be called after other font properties, as it references
332        // a CSSDefinition object
333        $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
334
335        // same here
336        $this->info['border'] =
337        $this->info['border-bottom'] =
338        $this->info['border-top'] =
339        $this->info['border-left'] =
340        $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
341
342        $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
343            array('collapse', 'separate')
344        );
345
346        $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
347            array('top', 'bottom')
348        );
349
350        $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
351            array('auto', 'fixed')
352        );
353
354        $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
355            array(
356                new HTMLPurifier_AttrDef_Enum(
357                    array(
358                        'baseline',
359                        'sub',
360                        'super',
361                        'top',
362                        'text-top',
363                        'middle',
364                        'bottom',
365                        'text-bottom'
366                    )
367                ),
368                new HTMLPurifier_AttrDef_CSS_Length(),
369                new HTMLPurifier_AttrDef_CSS_Percentage()
370            )
371        );
372
373        $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
374
375        // These CSS properties don't work on many browsers, but we live
376        // in THE FUTURE!
377        $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
378            array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
379        );
380
381        if ($config->get('CSS.Proprietary')) {
382            $this->doSetupProprietary($config);
383        }
384
385        if ($config->get('CSS.AllowTricky')) {
386            $this->doSetupTricky($config);
387        }
388
389        if ($config->get('CSS.Trusted')) {
390            $this->doSetupTrusted($config);
391        }
392
393        $allow_important = $config->get('CSS.AllowImportant');
394        // wrap all attr-defs with decorator that handles !important
395        foreach ($this->info as $k => $v) {
396            $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
397        }
398
399        $this->setupConfigStuff($config);
400    }
401
402    /**
403     * @param HTMLPurifier_Config $config
404     */
405    protected function doSetupProprietary($config)
406    {
407        // Internet Explorer only scrollbar colors
408        $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
409        $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
410        $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
411        $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
412        $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
413        $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
414
415        // vendor specific prefixes of opacity
416        $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
417        $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
418
419        // only opacity, for now
420        $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
421
422        // more CSS3
423        $this->info['page-break-after'] =
424        $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
425            array(
426                'auto',
427                'always',
428                'avoid',
429                'left',
430                'right'
431            )
432        );
433        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
434
435        $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
436            array(
437                new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
438                new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
439            ));
440
441        $this->info['border-top-left-radius'] =
442        $this->info['border-top-right-radius'] =
443        $this->info['border-bottom-right-radius'] =
444        $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
445        // TODO: support SLASH syntax
446        $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
447
448    }
449
450    /**
451     * @param HTMLPurifier_Config $config
452     */
453    protected function doSetupTricky($config)
454    {
455        $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
456            array(
457                'inline',
458                'block',
459                'list-item',
460                'run-in',
461                'compact',
462                'marker',
463                'table',
464                'inline-block',
465                'inline-table',
466                'table-row-group',
467                'table-header-group',
468                'table-footer-group',
469                'table-row',
470                'table-column-group',
471                'table-column',
472                'table-cell',
473                'table-caption',
474                'none'
475            )
476        );
477        $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
478            array('visible', 'hidden', 'collapse')
479        );
480        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
481        $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
482    }
483
484    /**
485     * @param HTMLPurifier_Config $config
486     */
487    protected function doSetupTrusted($config)
488    {
489        $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
490            array('static', 'relative', 'absolute', 'fixed')
491        );
492        $this->info['top'] =
493        $this->info['left'] =
494        $this->info['right'] =
495        $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
496            array(
497                new HTMLPurifier_AttrDef_CSS_Length(),
498                new HTMLPurifier_AttrDef_CSS_Percentage(),
499                new HTMLPurifier_AttrDef_Enum(array('auto')),
500            )
501        );
502        $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
503            array(
504                new HTMLPurifier_AttrDef_Integer(),
505                new HTMLPurifier_AttrDef_Enum(array('auto')),
506            )
507        );
508    }
509
510    /**
511     * Performs extra config-based processing. Based off of
512     * HTMLPurifier_HTMLDefinition.
513     * @param HTMLPurifier_Config $config
514     * @todo Refactor duplicate elements into common class (probably using
515     *       composition, not inheritance).
516     */
517    protected function setupConfigStuff($config)
518    {
519        // setup allowed elements
520        $support = "(for information on implementing this, see the " .
521            "support forums) ";
522        $allowed_properties = $config->get('CSS.AllowedProperties');
523        if ($allowed_properties !== null) {
524            foreach ($this->info as $name => $d) {
525                if (!isset($allowed_properties[$name])) {
526                    unset($this->info[$name]);
527                }
528                unset($allowed_properties[$name]);
529            }
530            // emit errors
531            foreach ($allowed_properties as $name => $d) {
532                // :TODO: Is this htmlspecialchars() call really necessary?
533                $name = htmlspecialchars($name);
534                trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
535            }
536        }
537
538        $forbidden_properties = $config->get('CSS.ForbiddenProperties');
539        if ($forbidden_properties !== null) {
540            foreach ($this->info as $name => $d) {
541                if (isset($forbidden_properties[$name])) {
542                    unset($this->info[$name]);
543                }
544            }
545        }
546    }
547}
548
549// vim: et sw=4 sts=4
550