]>
Commit | Line | Data |
---|---|---|
010efc9b AD |
1 | <?php |
2 | ||
3 | /** | |
4 | * Defines allowed CSS attributes and what their values are. | |
5 | * @see HTMLPurifier_HTMLDefinition | |
6 | */ | |
7 | class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition | |
8 | { | |
9 | ||
10 | public $type = 'CSS'; | |
11 | ||
12 | /** | |
13 | * Assoc array of attribute name to definition object. | |
14 | */ | |
15 | public $info = array(); | |
16 | ||
17 | /** | |
18 | * Constructs the info array. The meat of this class. | |
19 | */ | |
20 | protected function doSetup($config) { | |
21 | ||
22 | $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( | |
23 | array('left', 'right', 'center', 'justify'), false); | |
24 | ||
25 | $border_style = | |
26 | $this->info['border-bottom-style'] = | |
27 | $this->info['border-right-style'] = | |
28 | $this->info['border-left-style'] = | |
29 | $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( | |
30 | array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double', | |
31 | 'groove', 'ridge', 'inset', 'outset'), false); | |
32 | ||
33 | $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); | |
34 | ||
35 | $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( | |
36 | array('none', 'left', 'right', 'both'), false); | |
37 | $this->info['float'] = new HTMLPurifier_AttrDef_Enum( | |
38 | array('none', 'left', 'right'), false); | |
39 | $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( | |
40 | array('normal', 'italic', 'oblique'), false); | |
41 | $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( | |
42 | array('normal', 'small-caps'), false); | |
43 | ||
44 | $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( | |
45 | array( | |
46 | new HTMLPurifier_AttrDef_Enum(array('none')), | |
47 | new HTMLPurifier_AttrDef_CSS_URI() | |
48 | ) | |
49 | ); | |
50 | ||
51 | $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( | |
52 | array('inside', 'outside'), false); | |
53 | $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( | |
54 | array('disc', 'circle', 'square', 'decimal', 'lower-roman', | |
55 | 'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false); | |
56 | $this->info['list-style-image'] = $uri_or_none; | |
57 | ||
58 | $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); | |
59 | ||
60 | $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( | |
61 | array('capitalize', 'uppercase', 'lowercase', 'none'), false); | |
62 | $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
63 | ||
64 | $this->info['background-image'] = $uri_or_none; | |
65 | $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( | |
66 | array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') | |
67 | ); | |
68 | $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( | |
69 | array('scroll', 'fixed') | |
70 | ); | |
71 | $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); | |
72 | ||
73 | $border_color = | |
74 | $this->info['border-top-color'] = | |
75 | $this->info['border-bottom-color'] = | |
76 | $this->info['border-left-color'] = | |
77 | $this->info['border-right-color'] = | |
78 | $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
79 | new HTMLPurifier_AttrDef_Enum(array('transparent')), | |
80 | new HTMLPurifier_AttrDef_CSS_Color() | |
81 | )); | |
82 | ||
83 | $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); | |
84 | ||
85 | $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); | |
86 | ||
87 | $border_width = | |
88 | $this->info['border-top-width'] = | |
89 | $this->info['border-bottom-width'] = | |
90 | $this->info['border-left-width'] = | |
91 | $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
92 | new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), | |
93 | new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative | |
94 | )); | |
95 | ||
96 | $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); | |
97 | ||
98 | $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
99 | new HTMLPurifier_AttrDef_Enum(array('normal')), | |
100 | new HTMLPurifier_AttrDef_CSS_Length() | |
101 | )); | |
102 | ||
103 | $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
104 | new HTMLPurifier_AttrDef_Enum(array('normal')), | |
105 | new HTMLPurifier_AttrDef_CSS_Length() | |
106 | )); | |
107 | ||
108 | $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
109 | new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small', | |
110 | 'small', 'medium', 'large', 'x-large', 'xx-large', | |
111 | 'larger', 'smaller')), | |
112 | new HTMLPurifier_AttrDef_CSS_Percentage(), | |
113 | new HTMLPurifier_AttrDef_CSS_Length() | |
114 | )); | |
115 | ||
116 | $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
117 | new HTMLPurifier_AttrDef_Enum(array('normal')), | |
118 | new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives | |
119 | new HTMLPurifier_AttrDef_CSS_Length('0'), | |
120 | new HTMLPurifier_AttrDef_CSS_Percentage(true) | |
121 | )); | |
122 | ||
123 | $margin = | |
124 | $this->info['margin-top'] = | |
125 | $this->info['margin-bottom'] = | |
126 | $this->info['margin-left'] = | |
127 | $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
128 | new HTMLPurifier_AttrDef_CSS_Length(), | |
129 | new HTMLPurifier_AttrDef_CSS_Percentage(), | |
130 | new HTMLPurifier_AttrDef_Enum(array('auto')) | |
131 | )); | |
132 | ||
133 | $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); | |
134 | ||
135 | // non-negative | |
136 | $padding = | |
137 | $this->info['padding-top'] = | |
138 | $this->info['padding-bottom'] = | |
139 | $this->info['padding-left'] = | |
140 | $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
141 | new HTMLPurifier_AttrDef_CSS_Length('0'), | |
142 | new HTMLPurifier_AttrDef_CSS_Percentage(true) | |
143 | )); | |
144 | ||
145 | $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); | |
146 | ||
147 | $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
148 | new HTMLPurifier_AttrDef_CSS_Length(), | |
149 | new HTMLPurifier_AttrDef_CSS_Percentage() | |
150 | )); | |
151 | ||
152 | $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
153 | new HTMLPurifier_AttrDef_CSS_Length('0'), | |
154 | new HTMLPurifier_AttrDef_CSS_Percentage(true), | |
155 | new HTMLPurifier_AttrDef_Enum(array('auto')) | |
156 | )); | |
157 | $max = $config->get('CSS.MaxImgLength'); | |
158 | ||
159 | $this->info['width'] = | |
160 | $this->info['height'] = | |
161 | $max === null ? | |
162 | $trusted_wh : | |
163 | new HTMLPurifier_AttrDef_Switch('img', | |
164 | // For img tags: | |
165 | new HTMLPurifier_AttrDef_CSS_Composite(array( | |
166 | new HTMLPurifier_AttrDef_CSS_Length('0', $max), | |
167 | new HTMLPurifier_AttrDef_Enum(array('auto')) | |
168 | )), | |
169 | // For everyone else: | |
170 | $trusted_wh | |
171 | ); | |
172 | ||
173 | $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); | |
174 | ||
175 | $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); | |
176 | ||
177 | // this could use specialized code | |
178 | $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( | |
179 | array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300', | |
180 | '400', '500', '600', '700', '800', '900'), false); | |
181 | ||
182 | // MUST be called after other font properties, as it references | |
183 | // a CSSDefinition object | |
184 | $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); | |
185 | ||
186 | // same here | |
187 | $this->info['border'] = | |
188 | $this->info['border-bottom'] = | |
189 | $this->info['border-top'] = | |
190 | $this->info['border-left'] = | |
191 | $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); | |
192 | ||
193 | $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array( | |
194 | 'collapse', 'separate')); | |
195 | ||
196 | $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array( | |
197 | 'top', 'bottom')); | |
198 | ||
199 | $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array( | |
200 | 'auto', 'fixed')); | |
201 | ||
202 | $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
203 | new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super', | |
204 | 'top', 'text-top', 'middle', 'bottom', 'text-bottom')), | |
205 | new HTMLPurifier_AttrDef_CSS_Length(), | |
206 | new HTMLPurifier_AttrDef_CSS_Percentage() | |
207 | )); | |
208 | ||
209 | $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); | |
210 | ||
211 | // partial support | |
212 | $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap')); | |
213 | ||
214 | if ($config->get('CSS.Proprietary')) { | |
215 | $this->doSetupProprietary($config); | |
216 | } | |
217 | ||
218 | if ($config->get('CSS.AllowTricky')) { | |
219 | $this->doSetupTricky($config); | |
220 | } | |
221 | ||
222 | if ($config->get('CSS.Trusted')) { | |
223 | $this->doSetupTrusted($config); | |
224 | } | |
225 | ||
226 | $allow_important = $config->get('CSS.AllowImportant'); | |
227 | // wrap all attr-defs with decorator that handles !important | |
228 | foreach ($this->info as $k => $v) { | |
229 | $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); | |
230 | } | |
231 | ||
232 | $this->setupConfigStuff($config); | |
233 | } | |
234 | ||
235 | protected function doSetupProprietary($config) { | |
236 | // Internet Explorer only scrollbar colors | |
237 | $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
238 | $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
239 | $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
240 | $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
241 | $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
242 | $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |
243 | ||
244 | // technically not proprietary, but CSS3, and no one supports it | |
245 | $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |
246 | $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |
247 | $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |
248 | ||
249 | // only opacity, for now | |
250 | $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); | |
251 | ||
252 | } | |
253 | ||
254 | protected function doSetupTricky($config) { | |
255 | $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array( | |
256 | 'inline', 'block', 'list-item', 'run-in', 'compact', | |
257 | 'marker', 'table', 'inline-table', 'table-row-group', | |
258 | 'table-header-group', 'table-footer-group', 'table-row', | |
259 | 'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none' | |
260 | )); | |
261 | $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array( | |
262 | 'visible', 'hidden', 'collapse' | |
263 | )); | |
264 | $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); | |
265 | } | |
266 | ||
267 | protected function doSetupTrusted($config) { | |
268 | $this->info['position'] = new HTMLPurifier_AttrDef_Enum(array( | |
269 | 'static', 'relative', 'absolute', 'fixed' | |
270 | )); | |
271 | $this->info['top'] = | |
272 | $this->info['left'] = | |
273 | $this->info['right'] = | |
274 | $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
275 | new HTMLPurifier_AttrDef_CSS_Length(), | |
276 | new HTMLPurifier_AttrDef_CSS_Percentage(), | |
277 | new HTMLPurifier_AttrDef_Enum(array('auto')), | |
278 | )); | |
279 | $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(array( | |
280 | new HTMLPurifier_AttrDef_Integer(), | |
281 | new HTMLPurifier_AttrDef_Enum(array('auto')), | |
282 | )); | |
283 | } | |
284 | ||
285 | /** | |
286 | * Performs extra config-based processing. Based off of | |
287 | * HTMLPurifier_HTMLDefinition. | |
288 | * @todo Refactor duplicate elements into common class (probably using | |
289 | * composition, not inheritance). | |
290 | */ | |
291 | protected function setupConfigStuff($config) { | |
292 | ||
293 | // setup allowed elements | |
294 | $support = "(for information on implementing this, see the ". | |
295 | "support forums) "; | |
296 | $allowed_properties = $config->get('CSS.AllowedProperties'); | |
297 | if ($allowed_properties !== null) { | |
298 | foreach ($this->info as $name => $d) { | |
299 | if(!isset($allowed_properties[$name])) unset($this->info[$name]); | |
300 | unset($allowed_properties[$name]); | |
301 | } | |
302 | // emit errors | |
303 | foreach ($allowed_properties as $name => $d) { | |
304 | // :TODO: Is this htmlspecialchars() call really necessary? | |
305 | $name = htmlspecialchars($name); | |
306 | trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); | |
307 | } | |
308 | } | |
309 | ||
310 | $forbidden_properties = $config->get('CSS.ForbiddenProperties'); | |
311 | if ($forbidden_properties !== null) { | |
312 | foreach ($this->info as $name => $d) { | |
313 | if (isset($forbidden_properties[$name])) { | |
314 | unset($this->info[$name]); | |
315 | } | |
316 | } | |
317 | } | |
318 | ||
319 | } | |
320 | } | |
321 | ||
322 | // vim: et sw=4 sts=4 |