Index: branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php |
— | — | @@ -10,145 +10,200 @@ |
11 | 11 | |
12 | 12 | class GadgetPrefs { |
13 | 13 | |
14 | | - //Syntax specifications of preference description language. |
15 | | - //Each element describes a type, and it has a 'description' and may have a 'checker'. |
16 | | - // - 'description' is an array that describes the fields of that option. |
17 | | - // - 'checker' is an optional function that does validation of the entire preference description, |
18 | | - // when more complex semantics are needed. |
| 14 | + /* |
| 15 | + * Syntax specifications of preference description language. |
| 16 | + * Each element describes a field; a "simple" field encodes exactly one gadget preference, but some fields |
| 17 | + * may encode for 0 or multiple gadget preferences. |
| 18 | + * Each field has a 'description' and may have a 'validator', a 'flattener', and a 'checker'. |
| 19 | + * - 'description' is an array that describes all the members of that fields. Each member description has this shape: |
| 20 | + * - 'isMandatory' is a boolean that specifies if that member is mandatory for the field; |
| 21 | + * - 'validator', if specified, is the name of a function that validates that member |
| 22 | + * - 'validator' is an optional function that does validation of the entire field description, |
| 23 | + * when member validators does not suffice since more complex semantics are needed. |
| 24 | + * - 'flattener' is an optional function that takes a valid field description and returns an array of specification of |
| 25 | + * gadget preferences, with preference names as keys and corresponding "simple" field descriptions as values. |
| 26 | + * If omitted (for "simple fields"), the default flattener is used. |
| 27 | + * - 'checker', only for "simple" fields, is the name of a function that takes a preference description and |
| 28 | + * a preference value, and returns true if that value passes validation, false otherwise. |
| 29 | + * - 'getMessages', if specified, is the name of a function that takes a valid description of a field and returns |
| 30 | + * a list of messages referred to by it. If omitted, only the "label" field is returned (if it is a message). |
| 31 | + */ |
19 | 32 | private static $prefsDescriptionSpecifications = array( |
20 | 33 | 'boolean' => array( |
21 | 34 | 'description' => array( |
| 35 | + 'name' => array( |
| 36 | + 'isMandatory' => true, |
| 37 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 38 | + ), |
22 | 39 | 'default' => array( |
23 | 40 | 'isMandatory' => true, |
24 | | - 'checker' => 'is_bool' |
| 41 | + 'validator' => 'is_bool' |
25 | 42 | ), |
26 | 43 | 'label' => array( |
27 | 44 | 'isMandatory' => true, |
28 | | - 'checker' => 'is_string' |
| 45 | + 'validator' => 'is_string' |
29 | 46 | ) |
30 | | - ) |
| 47 | + ), |
| 48 | + 'checker' => 'GadgetPrefs::checkBooleanPref' |
31 | 49 | ), |
32 | 50 | 'string' => array( |
33 | 51 | 'description' => array( |
| 52 | + 'name' => array( |
| 53 | + 'isMandatory' => true, |
| 54 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 55 | + ), |
34 | 56 | 'default' => array( |
35 | 57 | 'isMandatory' => true, |
36 | | - 'checker' => 'is_string' |
| 58 | + 'validator' => 'is_string' |
37 | 59 | ), |
38 | 60 | 'label' => array( |
39 | 61 | 'isMandatory' => true, |
40 | | - 'checker' => 'is_string' |
| 62 | + 'validator' => 'is_string' |
41 | 63 | ), |
42 | 64 | 'required' => array( |
43 | 65 | 'isMandatory' => false, |
44 | | - 'checker' => 'is_bool' |
| 66 | + 'validator' => 'is_bool' |
45 | 67 | ), |
46 | 68 | 'minlength' => array( |
47 | 69 | 'isMandatory' => false, |
48 | | - 'checker' => 'is_integer' |
| 70 | + 'validator' => 'is_integer' |
49 | 71 | ), |
50 | 72 | 'maxlength' => array( |
51 | 73 | 'isMandatory' => false, |
52 | | - 'checker' => 'is_integer' |
| 74 | + 'validator' => 'is_integer' |
53 | 75 | ) |
54 | 76 | ), |
55 | | - 'checker' => 'GadgetPrefs::checkStringOptionDefinition' |
| 77 | + 'validator' => 'GadgetPrefs::validateStringOptionDefinition', |
| 78 | + 'checker' => 'GadgetPrefs::checkStringPref' |
56 | 79 | ), |
57 | 80 | 'number' => array( |
58 | 81 | 'description' => array( |
| 82 | + 'name' => array( |
| 83 | + 'isMandatory' => true, |
| 84 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 85 | + ), |
59 | 86 | 'default' => array( |
60 | 87 | 'isMandatory' => true, |
61 | | - 'checker' => 'GadgetPrefs::isFloatOrIntOrNull' |
| 88 | + 'validator' => 'GadgetPrefs::isFloatOrIntOrNull' |
62 | 89 | ), |
63 | 90 | 'label' => array( |
64 | 91 | 'isMandatory' => true, |
65 | | - 'checker' => 'is_string' |
| 92 | + 'validator' => 'is_string' |
66 | 93 | ), |
67 | 94 | 'required' => array( |
68 | 95 | 'isMandatory' => false, |
69 | | - 'checker' => 'is_bool' |
| 96 | + 'validator' => 'is_bool' |
70 | 97 | ), |
71 | 98 | 'integer' => array( |
72 | 99 | 'isMandatory' => false, |
73 | | - 'checker' => 'is_bool' |
| 100 | + 'validator' => 'is_bool' |
74 | 101 | ), |
75 | 102 | 'min' => array( |
76 | 103 | 'isMandatory' => false, |
77 | | - 'checker' => 'GadgetPrefs::isFloatOrInt' |
| 104 | + 'validator' => 'GadgetPrefs::isFloatOrInt' |
78 | 105 | ), |
79 | 106 | 'max' => array( |
80 | 107 | 'isMandatory' => false, |
81 | | - 'checker' => 'GadgetPrefs::isFloatOrInt' |
| 108 | + 'validator' => 'GadgetPrefs::isFloatOrInt' |
82 | 109 | ) |
83 | 110 | ), |
84 | | - 'checker' => 'GadgetPrefs::checkNumberOptionDefinition' |
| 111 | + 'validator' => 'GadgetPrefs::validateNumberOptionDefinition', |
| 112 | + 'checker' => 'GadgetPrefs::checkNumberPref' |
85 | 113 | ), |
86 | 114 | 'select' => array( |
87 | 115 | 'description' => array( |
| 116 | + 'name' => array( |
| 117 | + 'isMandatory' => true, |
| 118 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 119 | + ), |
88 | 120 | 'default' => array( |
89 | 121 | 'isMandatory' => true |
90 | 122 | ), |
91 | 123 | 'label' => array( |
92 | 124 | 'isMandatory' => true, |
93 | | - 'checker' => 'is_string' |
| 125 | + 'validator' => 'is_string' |
94 | 126 | ), |
95 | 127 | 'options' => array( |
96 | 128 | 'isMandatory' => true, |
97 | | - 'checker' => 'is_array' |
| 129 | + 'validator' => 'is_array' |
98 | 130 | ) |
99 | 131 | ), |
100 | | - 'checker' => 'GadgetPrefs::checkSelectOptionDefinition' |
| 132 | + 'validator' => 'GadgetPrefs::validateSelectOptionDefinition', |
| 133 | + 'checker' => 'GadgetPrefs::checkSelectPref', |
| 134 | + 'getMessages' => 'GadgetPrefs::getSelectMessages' |
101 | 135 | ), |
102 | 136 | 'range' => array( |
103 | 137 | 'description' => array( |
| 138 | + 'name' => array( |
| 139 | + 'isMandatory' => true, |
| 140 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 141 | + ), |
104 | 142 | 'default' => array( |
105 | 143 | 'isMandatory' => true, |
106 | | - 'checker' => 'GadgetPrefs::isFloatOrIntOrNull' |
| 144 | + 'validator' => 'GadgetPrefs::isFloatOrIntOrNull' |
107 | 145 | ), |
108 | 146 | 'label' => array( |
109 | 147 | 'isMandatory' => true, |
110 | | - 'checker' => 'is_string' |
| 148 | + 'validator' => 'is_string' |
111 | 149 | ), |
112 | 150 | 'min' => array( |
113 | 151 | 'isMandatory' => true, |
114 | | - 'checker' => 'GadgetPrefs::isFloatOrInt' |
| 152 | + 'validator' => 'GadgetPrefs::isFloatOrInt' |
115 | 153 | ), |
116 | 154 | 'max' => array( |
117 | 155 | 'isMandatory' => true, |
118 | | - 'checker' => 'GadgetPrefs::isFloatOrInt' |
| 156 | + 'validator' => 'GadgetPrefs::isFloatOrInt' |
119 | 157 | ), |
120 | 158 | 'step' => array( |
121 | 159 | 'isMandatory' => false, |
122 | | - 'checker' => 'GadgetPrefs::isFloatOrInt' |
| 160 | + 'validator' => 'GadgetPrefs::isFloatOrInt' |
123 | 161 | ) |
124 | 162 | ), |
125 | | - 'checker' => 'GadgetPrefs::checkRangeOptionDefinition' |
| 163 | + 'validator' => 'GadgetPrefs::validateRangeOptionDefinition', |
| 164 | + 'checker' => 'GadgetPrefs::checkRangePref' |
126 | 165 | ), |
127 | 166 | 'date' => array( |
128 | 167 | 'description' => array( |
| 168 | + 'name' => array( |
| 169 | + 'isMandatory' => true, |
| 170 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 171 | + ), |
129 | 172 | 'default' => array( |
130 | 173 | 'isMandatory' => true |
131 | 174 | ), |
132 | 175 | 'label' => array( |
133 | 176 | 'isMandatory' => true, |
134 | | - 'checker' => 'is_string' |
| 177 | + 'validator' => 'is_string' |
135 | 178 | ) |
136 | | - ) |
| 179 | + ), |
| 180 | + 'checker' => 'GadgetPrefs::checkDatePref' |
137 | 181 | ), |
138 | 182 | 'color' => array( |
139 | 183 | 'description' => array( |
| 184 | + 'name' => array( |
| 185 | + 'isMandatory' => true, |
| 186 | + 'validator' => 'GadgetPrefs::isValidPreferenceName' |
| 187 | + ), |
140 | 188 | 'default' => array( |
141 | 189 | 'isMandatory' => true |
142 | 190 | ), |
143 | 191 | 'label' => array( |
144 | 192 | 'isMandatory' => true, |
145 | | - 'checker' => 'is_string' |
| 193 | + 'validator' => 'is_string' |
146 | 194 | ) |
147 | | - ) |
| 195 | + ), |
| 196 | + 'checker' => 'GadgetPrefs::checkColorPref' |
148 | 197 | ) |
149 | 198 | ); |
150 | 199 | |
| 200 | + private static function isValidPreferenceName( $name ) { |
| 201 | + return strlen( $name ) <= 40 |
| 202 | + && preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $name ); |
| 203 | + } |
| 204 | + |
| 205 | + |
151 | 206 | //Further checks for 'string' options |
152 | | - private static function checkStringOptionDefinition( $option ) { |
| 207 | + private static function validateStringOptionDefinition( $option ) { |
153 | 208 | if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) { |
154 | 209 | return false; |
155 | 210 | } |
— | — | @@ -174,8 +229,13 @@ |
175 | 230 | return is_float( $param ) || is_int( $param ) || $param === null; |
176 | 231 | } |
177 | 232 | |
| 233 | + //default flattener for simple fields that encode for a single preference |
| 234 | + private static function flattenSimpleField( $fieldDescription ) { |
| 235 | + return array( $fieldDescription['name'] => $fieldDescription ); |
| 236 | + } |
| 237 | + |
178 | 238 | //Further checks for 'number' options |
179 | | - private static function checkNumberOptionDefinition( $option ) { |
| 239 | + private static function validateNumberOptionDefinition( $option ) { |
180 | 240 | if ( isset( $option['integer'] ) && $option['integer'] === true ) { |
181 | 241 | //Check if 'min', 'max' and 'default' are integers (if given) |
182 | 242 | if ( intval( $option['default'] ) != $option['default'] ) { |
— | — | @@ -192,7 +252,7 @@ |
193 | 253 | return true; |
194 | 254 | } |
195 | 255 | |
196 | | - private static function checkSelectOptionDefinition( $option ) { |
| 256 | + private static function validateSelectOptionDefinition( $option ) { |
197 | 257 | $options = $option['options']; |
198 | 258 | |
199 | 259 | foreach ( $options as $opt => $optVal ) { |
— | — | @@ -212,7 +272,7 @@ |
213 | 273 | return true; |
214 | 274 | } |
215 | 275 | |
216 | | - private static function checkRangeOptionDefinition( $option ) { |
| 276 | + private static function validateRangeOptionDefinition( $option ) { |
217 | 277 | $step = isset( $option['step'] ) ? $option['step'] : 1; |
218 | 278 | |
219 | 279 | if ( $step <= 0 ) { |
— | — | @@ -233,42 +293,71 @@ |
234 | 294 | |
235 | 295 | return true; |
236 | 296 | } |
237 | | - |
238 | | - //Checks if the given description of the preferences is valid |
239 | | - public static function isPrefsDescriptionValid( $prefsDescription ) { |
240 | | - if ( !is_array( $prefsDescription ) |
241 | | - || !isset( $prefsDescription['fields'] ) |
242 | | - || !is_array( $prefsDescription['fields'] ) ) |
| 297 | + |
| 298 | + //Flattens a simple field, by calling its field-specific flattener if there is any, |
| 299 | + //or the default flattener otherwise. |
| 300 | + private static function flattenFieldDescription( $fieldDescription ) { |
| 301 | + $typeSpec = self::$prefsDescriptionSpecifications[$fieldDescription['type']]; |
| 302 | + $typeDescription = $typeSpec['description']; |
| 303 | + if ( isset( $typeSpec['flattener'] ) ) { |
| 304 | + $flattener = $typeSpec['flattener']; |
| 305 | + } else { |
| 306 | + $flattener = 'GadgetPrefs::flattenSimpleField'; |
| 307 | + } |
| 308 | + return call_user_func( $flattener, $fieldDescription ); |
| 309 | + } |
| 310 | + |
| 311 | + //Returns a map keyed at preference names, and with their corresponding |
| 312 | + //"simple" field descriptions as values. |
| 313 | + //It is assumed that $prefsDescription is valid. |
| 314 | + private static function flattenPrefsDescription( $prefsDescription ) { |
| 315 | + $flattenedPrefsDescription = array(); |
| 316 | + foreach ( $prefsDescription['fields'] as $fieldDescription ) { |
| 317 | + $flt = self::flattenFieldDescription( $fieldDescription ); |
| 318 | + $flattenedPrefsDescription = array_merge( $flattenedPrefsDescription, $flt ); |
| 319 | + } |
| 320 | + return $flattenedPrefsDescription; |
| 321 | + } |
| 322 | + |
| 323 | + //Validate the description of a 'section' of preferences |
| 324 | + private static function validateSectionDefinition( $sectionDescription ) { |
| 325 | + static $mandatoryCount = array(), $initialized = false; |
| 326 | + |
| 327 | + if ( !is_array( $sectionDescription ) |
| 328 | + || !isset( $sectionDescription['fields'] ) |
| 329 | + || !is_array( $sectionDescription['fields'] ) ) |
243 | 330 | { |
244 | 331 | return false; |
245 | 332 | } |
246 | 333 | |
| 334 | + if ( !$initialized ) { |
| 335 | + //Count of mandatory members for each type |
| 336 | + foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) { |
| 337 | + $mandatoryCount[$type] = 0; |
| 338 | + foreach ( $typeSpec['description'] as $fieldName => $fieldSpec ) { |
| 339 | + if ( $fieldSpec['isMandatory'] === true ) { |
| 340 | + ++$mandatoryCount[$type]; |
| 341 | + } |
| 342 | + } |
| 343 | + } |
| 344 | + $initialized = true; |
| 345 | + } |
| 346 | + |
247 | 347 | //Check if 'fields' is a regular (not-associative) array, and that it is not empty |
248 | | - $count = count( $prefsDescription['fields'] ); |
249 | | - if ( $count == 0 || array_keys( $prefsDescription['fields'] ) !== range( 0, $count - 1 ) ) { |
| 348 | + $count = count( $sectionDescription['fields'] ); |
| 349 | + if ( $count == 0 || array_keys( $sectionDescription['fields'] ) !== range( 0, $count - 1 ) ) { |
250 | 350 | return false; |
251 | 351 | } |
252 | 352 | |
253 | | - //Count of mandatory members for each type |
254 | | - $mandatoryCount = array(); |
255 | | - foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) { |
256 | | - $mandatoryCount[$type] = 0; |
257 | | - foreach ( $typeSpec['description'] as $fieldName => $fieldSpec ) { |
258 | | - if ( $fieldSpec['isMandatory'] === true ) { |
259 | | - ++$mandatoryCount[$type]; |
260 | | - } |
261 | | - } |
262 | | - } |
263 | | - |
264 | 353 | //TODO: validation of members other than $prefs['fields'] |
265 | 354 | |
266 | | - //Map of encountered names |
267 | | - $names = array(); |
| 355 | + //Flattened preferences |
| 356 | + $flattenedPrefs = array(); |
268 | 357 | |
269 | | - foreach ( $prefsDescription['fields'] as $optionDefinition ) { |
| 358 | + foreach ( $sectionDescription['fields'] as $optionDefinition ) { |
270 | 359 | |
271 | | - //Check if 'name' and 'type' are set |
272 | | - if ( !isset( $optionDefinition['type'] ) || !isset( $optionDefinition['name'] ) ) { |
| 360 | + //Check if 'type' is set |
| 361 | + if ( !isset( $optionDefinition['type'] ) ) { |
273 | 362 | return false; |
274 | 363 | } |
275 | 364 | |
— | — | @@ -279,30 +368,14 @@ |
280 | 369 | return false; |
281 | 370 | } |
282 | 371 | |
283 | | - $option = $optionDefinition['name']; |
284 | | - |
285 | | - //check that it's different from previous names |
286 | | - if ( isset( $names[$option] ) ) { |
287 | | - return false; |
288 | | - } |
289 | | - |
290 | | - $names[$option] = true; |
291 | | - |
292 | | - //check option name compliance |
293 | | - if ( strlen( $option ) > 40 |
294 | | - || !preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $option ) ) |
295 | | - { |
296 | | - return false; |
297 | | - } |
298 | | - |
299 | 372 | //Check if all fields satisfy specification |
300 | 373 | $typeSpec = self::$prefsDescriptionSpecifications[$type]; |
301 | 374 | $typeDescription = $typeSpec['description']; |
302 | 375 | $count = 0; //count of present mandatory members |
303 | 376 | foreach ( $optionDefinition as $fieldName => $fieldValue ) { |
304 | 377 | |
305 | | - if ( $fieldName == 'type' || $fieldName == 'name' ) { |
306 | | - continue; //'type' and 'name' must not be checked |
| 378 | + if ( $fieldName == 'type' ) { |
| 379 | + continue; //'type' must not be checked |
307 | 380 | } |
308 | 381 | |
309 | 382 | if ( !isset( $typeDescription[$fieldName] ) ) { |
— | — | @@ -313,9 +386,9 @@ |
314 | 387 | ++$count; |
315 | 388 | } |
316 | 389 | |
317 | | - if ( isset( $typeDescription[$fieldName]['checker'] ) ) { |
318 | | - $checker = $typeDescription[$fieldName]['checker']; |
319 | | - if ( !call_user_func( $checker, $fieldValue ) ) { |
| 390 | + if ( isset( $typeDescription[$fieldName]['validator'] ) ) { |
| 391 | + $validator = $typeDescription[$fieldName]['validator']; |
| 392 | + if ( !call_user_func( $validator, $fieldValue ) ) { |
320 | 393 | return false; |
321 | 394 | } |
322 | 395 | } |
— | — | @@ -325,30 +398,48 @@ |
326 | 399 | return false; //not all mandatory members are given |
327 | 400 | } |
328 | 401 | |
329 | | - if ( isset( $typeSpec['checker'] ) ) { |
| 402 | + if ( isset( $typeSpec['validator'] ) ) { |
330 | 403 | //Call type-specific checker for finer validation |
331 | | - if ( !call_user_func( $typeSpec['checker'], $optionDefinition ) ) { |
| 404 | + if ( !call_user_func( $typeSpec['validator'], $optionDefinition ) ) { |
332 | 405 | return false; |
333 | 406 | } |
334 | 407 | } |
| 408 | + |
| 409 | + //flatten preferences described by this field |
| 410 | + $flt = self::flattenFieldDescription( $optionDefinition ); |
335 | 411 | |
336 | | - //Finally, check that the 'default' fields exists and is valid |
337 | | - if ( !array_key_exists( 'default', $optionDefinition ) ) { |
338 | | - return false; |
| 412 | + foreach ( $flt as $prefName => $prefDescription ) { |
| 413 | + //Finally, check that the 'default' fields exists and is valid |
| 414 | + //for all preferences encoded by this field |
| 415 | + if ( !array_key_exists( 'default', $prefDescription ) ) { |
| 416 | + return false; |
| 417 | + } |
| 418 | + |
| 419 | + $prefs = array( 'dummy' => $optionDefinition['default'] ); |
| 420 | + if ( !self::checkSinglePref( $optionDefinition, $prefs, 'dummy' ) ) { |
| 421 | + return false; |
| 422 | + } |
339 | 423 | } |
340 | 424 | |
341 | | - $prefs = array( 'dummy' => $optionDefinition['default'] ); |
342 | | - if ( !self::checkSinglePref( $optionDefinition, $prefs, 'dummy' ) ) { |
| 425 | + //If there are preferences with the same name of a previously encountered preference, fail |
| 426 | + if ( array_intersect( array_keys( $flt ), array_keys( $flattenedPrefs ) ) ) { |
343 | 427 | return false; |
344 | 428 | } |
| 429 | + $flattenedPrefs = array_merge( $flattenedPrefs, $flt ); |
345 | 430 | } |
346 | 431 | |
347 | 432 | return true; |
348 | 433 | } |
349 | 434 | |
350 | | - //Check if a preference is valid, according to description |
| 435 | + //Checks if the given description of the preferences is valid |
| 436 | + public static function isPrefsDescriptionValid( $prefsDescription ) { |
| 437 | + return self::validateSectionDefinition( $prefsDescription ); |
| 438 | + } |
| 439 | + |
| 440 | + //Check if a preference is valid, according to description. |
| 441 | + //$prefDescription must be the description of a "simple" field (that is, with 'checker') |
351 | 442 | //NOTE: we pass both $prefs and $prefName (instead of just $prefs[$prefName]) |
352 | | - // to allow checking for null. |
| 443 | + // to allow checking for undefined values. |
353 | 444 | private static function checkSinglePref( $prefDescription, $prefs, $prefName ) { |
354 | 445 | |
355 | 446 | //isset( $prefs[$prefName] ) would return false for null values |
— | — | @@ -356,129 +447,158 @@ |
357 | 448 | return false; |
358 | 449 | } |
359 | 450 | |
360 | | - $pref = $prefs[$prefName]; |
361 | | - |
362 | | - switch ( $prefDescription['type'] ) { |
363 | | - case 'boolean': |
364 | | - return is_bool( $pref ); |
365 | | - case 'string': |
366 | | - if ( !is_string( $pref ) ) { |
367 | | - return false; |
368 | | - } |
369 | | - |
370 | | - $len = strlen( $pref ); |
371 | | - |
372 | | - //Checks the "required" option, if present |
373 | | - $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true; |
374 | | - if ( $required === true && $len == 0 ) { |
375 | | - return false; |
376 | | - } elseif ( $required === false && $len == 0 ) { |
377 | | - return true; //overriding 'minlength' |
378 | | - } |
379 | | - |
380 | | - //Checks the "minlength" option, if present |
381 | | - $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0; |
382 | | - if ( $len < $minlength ){ |
383 | | - return false; |
384 | | - } |
| 451 | + $value = $prefs[$prefName]; |
| 452 | + $type = $prefDescription['type']; |
| 453 | + |
| 454 | + if ( !isset( self::$prefsDescriptionSpecifications[$type] ) |
| 455 | + || !isset( self::$prefsDescriptionSpecifications[$type]['checker'] ) ) |
| 456 | + { |
| 457 | + return false; |
| 458 | + } |
| 459 | + |
| 460 | + $checker = self::$prefsDescriptionSpecifications[$type]['checker']; |
| 461 | + return call_user_func( $checker, $prefDescription, $value ); |
| 462 | + } |
385 | 463 | |
386 | | - //Checks the "maxlength" option, if present |
387 | | - $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here? |
388 | | - if ( $len > $maxlength ){ |
389 | | - return false; |
390 | | - } |
391 | | - |
392 | | - return true; |
393 | | - case 'number': |
394 | | - if ( !is_float( $pref ) && !is_int( $pref ) && $pref !== null ) { |
395 | | - return false; |
396 | | - } |
| 464 | + //Checker for 'boolean' preferences |
| 465 | + private static function checkBooleanPref( $prefDescription, $value ) { |
| 466 | + return is_bool( $value ); |
| 467 | + } |
397 | 468 | |
398 | | - $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true; |
399 | | - if ( $required === false && $pref === null ) { |
400 | | - return true; |
401 | | - } |
402 | | - |
403 | | - if ( $pref === null ) { |
404 | | - return false; //$required === true, so null is not acceptable |
405 | | - } |
| 469 | + //Checker for 'string' preferences |
| 470 | + private static function checkStringPref( $prefDescription, $value ) { |
| 471 | + if ( !is_string( $value ) ) { |
| 472 | + return false; |
| 473 | + } |
| 474 | + |
| 475 | + $len = strlen( $value ); |
| 476 | + |
| 477 | + //Checks the "required" option, if present |
| 478 | + $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true; |
| 479 | + if ( $required === true && $len == 0 ) { |
| 480 | + return false; |
| 481 | + } elseif ( $required === false && $len == 0 ) { |
| 482 | + return true; //overriding 'minlength' |
| 483 | + } |
| 484 | + |
| 485 | + //Checks the "minlength" option, if present |
| 486 | + $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0; |
| 487 | + if ( $len < $minlength ){ |
| 488 | + return false; |
| 489 | + } |
406 | 490 | |
407 | | - $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false; |
408 | | - |
409 | | - if ( $integer === true && intval( $pref ) != $pref ) { |
410 | | - return false; //not integer |
411 | | - } |
412 | | - |
413 | | - if ( isset( $prefDescription['min'] ) ) { |
414 | | - $min = $prefDescription['min']; |
415 | | - if ( $pref < $min ) { |
416 | | - return false; //value below minimum |
417 | | - } |
418 | | - } |
| 491 | + //Checks the "maxlength" option, if present |
| 492 | + $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here? |
| 493 | + if ( $len > $maxlength ){ |
| 494 | + return false; |
| 495 | + } |
| 496 | + |
| 497 | + return true; |
| 498 | + } |
419 | 499 | |
420 | | - if ( isset( $prefDescription['max'] ) ) { |
421 | | - $max = $prefDescription['max']; |
422 | | - if ( $pref > $max ) { |
423 | | - return false; //value above maximum |
424 | | - } |
425 | | - } |
| 500 | + //Checker for 'number' preferences |
| 501 | + private static function checkNumberPref( $prefDescription, $value ) { |
| 502 | + if ( !is_float( $value ) && !is_int( $value ) && $value !== null ) { |
| 503 | + return false; |
| 504 | + } |
426 | 505 | |
427 | | - return true; |
428 | | - case 'select': |
429 | | - $values = array_values( $prefDescription['options'] ); |
430 | | - return in_array( $pref, $values, true ); |
431 | | - case 'range': |
432 | | - if ( !is_float( $pref ) && !is_int( $pref ) ) { |
433 | | - return false; |
434 | | - } |
435 | | - |
436 | | - $min = $prefDescription['min']; |
437 | | - $max = $prefDescription['max']; |
438 | | - |
439 | | - if ( $pref < $min || $pref > $max ) { |
440 | | - return false; |
441 | | - } |
442 | | - |
443 | | - $step = isset( $prefDescription['step'] ) ? $prefDescription['step'] : 1; |
444 | | - |
445 | | - if ( $step <= 0 ) { |
446 | | - return false; |
447 | | - } |
448 | | - |
449 | | - //Valid values are min, min + step, min + 2*step, ... |
450 | | - //Then ( $pref - $min ) / $step must be close enough to an integer |
451 | | - $eps = 1.0e-6; //tolerance |
452 | | - $tmp = ( $pref - $min ) / $step; |
453 | | - if ( abs( $tmp - floor( $tmp ) ) > $eps ) { |
454 | | - return false; |
455 | | - } |
456 | | - |
457 | | - return true; |
458 | | - case 'date': |
459 | | - if ( $pref === null ) { |
460 | | - return true; |
461 | | - } |
462 | | - |
463 | | - //Basic syntactic checks |
464 | | - if ( !is_string( $pref ) || |
465 | | - !preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/', $pref ) ) |
466 | | - { |
467 | | - return false; |
468 | | - } |
469 | | - |
470 | | - //Full parsing |
471 | | - return date_create( $pref ) !== false; |
472 | | - case 'color': |
473 | | - //Check if it's a string representing a color |
474 | | - //(with 6 hexadecimal lowercase characters). |
475 | | - return is_string( $pref ) && preg_match( '/^#[0-9a-f]{6}$/', $pref ); |
476 | | - default: |
477 | | - return false; //unexisting type |
| 506 | + $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true; |
| 507 | + if ( $required === false && $value === null ) { |
| 508 | + return true; |
478 | 509 | } |
| 510 | + |
| 511 | + if ( $value === null ) { |
| 512 | + return false; //$required === true, so null is not acceptable |
| 513 | + } |
| 514 | + |
| 515 | + $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false; |
| 516 | + |
| 517 | + if ( $integer === true && intval( $value ) != $value ) { |
| 518 | + return false; //not integer |
| 519 | + } |
| 520 | + |
| 521 | + if ( isset( $prefDescription['min'] ) ) { |
| 522 | + $min = $prefDescription['min']; |
| 523 | + if ( $value < $min ) { |
| 524 | + return false; //value below minimum |
| 525 | + } |
| 526 | + } |
| 527 | + |
| 528 | + if ( isset( $prefDescription['max'] ) ) { |
| 529 | + $max = $prefDescription['max']; |
| 530 | + if ( $value > $max ) { |
| 531 | + return false; //value above maximum |
| 532 | + } |
| 533 | + } |
| 534 | + |
| 535 | + return true; |
479 | 536 | } |
480 | 537 | |
| 538 | + //Checker for 'select' preferences |
| 539 | + private static function checkSelectPref( $prefDescription, $value ) { |
| 540 | + $values = array_values( $prefDescription['options'] ); |
| 541 | + return in_array( $value, $values, true ); |
| 542 | + } |
| 543 | + |
| 544 | + //Checker for 'range' preferences |
| 545 | + private static function checkRangePref( $prefDescription, $value ) { |
| 546 | + if ( !is_float( $value ) && !is_int( $value ) ) { |
| 547 | + return false; |
| 548 | + } |
| 549 | + |
| 550 | + $min = $prefDescription['min']; |
| 551 | + $max = $prefDescription['max']; |
| 552 | + |
| 553 | + if ( $value < $min || $value > $max ) { |
| 554 | + return false; |
| 555 | + } |
| 556 | + |
| 557 | + $step = isset( $prefDescription['step'] ) ? $prefDescription['step'] : 1; |
| 558 | + |
| 559 | + if ( $step <= 0 ) { |
| 560 | + return false; |
| 561 | + } |
| 562 | + |
| 563 | + //Valid values are min, min + step, min + 2*step, ... |
| 564 | + //Then ( $value - $min ) / $step must be close enough to an integer |
| 565 | + $eps = 1.0e-6; //tolerance |
| 566 | + $tmp = ( $value - $min ) / $step; |
| 567 | + if ( abs( $tmp - floor( $tmp ) ) > $eps ) { |
| 568 | + return false; |
| 569 | + } |
| 570 | + |
| 571 | + return true; |
| 572 | + } |
| 573 | + |
| 574 | + //Checker for 'date' preferences |
| 575 | + private static function checkDatePref( $prefDescription, $value ) { |
| 576 | + if ( $value === null ) { |
| 577 | + return true; |
| 578 | + } |
| 579 | + |
| 580 | + //Basic syntactic checks |
| 581 | + if ( !is_string( $value ) || |
| 582 | + !preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/', $value ) ) |
| 583 | + { |
| 584 | + return false; |
| 585 | + } |
| 586 | + |
| 587 | + //Full parsing |
| 588 | + return date_create( $value ) !== false; |
| 589 | + } |
| 590 | + |
| 591 | + //Checker for 'color' preferences |
| 592 | + private static function checkColorPref( $prefDescription, $value ) { |
| 593 | + //Check if it's a string representing a color |
| 594 | + //(with 6 hexadecimal lowercase characters). |
| 595 | + return is_string( $value ) && preg_match( '/^#[0-9a-f]{6}$/', $value ); |
| 596 | + } |
| 597 | + |
| 598 | + |
| 599 | + |
481 | 600 | /** |
482 | | - * Checks if $prefs is an array of preferences that passes validation |
| 601 | + * Checks if $prefs is an array of preferences that passes validation. |
| 602 | + * It is assumed that $prefsDescription is a valid description of preferences. |
483 | 603 | * |
484 | 604 | * @param $prefsDescription Array: the preferences description to use. |
485 | 605 | * @param $prefs Array: reference of the array of preferences to check. |
— | — | @@ -486,9 +606,10 @@ |
487 | 607 | * @return boolean true if $prefs passes validation against $prefsDescription, false otherwise. |
488 | 608 | */ |
489 | 609 | public static function checkPrefsAgainstDescription( $prefsDescription, $prefs ) { |
| 610 | + $flattenedPrefs = self::flattenPrefsDescription( $prefsDescription ); |
490 | 611 | $validPrefs = array(); |
491 | 612 | //Check that all the given preferences pass validation |
492 | | - foreach ( $prefsDescription['fields'] as $prefDescription ) { |
| 613 | + foreach ( $flattenedPrefs as $prefDescription ) { |
493 | 614 | $prefName = $prefDescription['name']; |
494 | 615 | if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) { |
495 | 616 | return false; |
— | — | @@ -509,15 +630,17 @@ |
510 | 631 | /** |
511 | 632 | * Fixes $prefs so that it matches the description given by $prefsDescription. |
512 | 633 | * All values of $prefs that fail validation are replaced with default values. |
| 634 | + * It is assumed that $prefsDescription is a valid description of preferences. |
513 | 635 | * |
514 | 636 | * @param $prefsDescription Array: the preferences description to use. |
515 | 637 | * @param &$prefs Array: reference of the array of preferences to match. |
516 | 638 | */ |
517 | 639 | public static function matchPrefsWithDescription( $prefsDescription, &$prefs ) { |
| 640 | + $flattenedPrefs = self::flattenPrefsDescription( $prefsDescription ); |
518 | 641 | $validPrefs = array(); |
519 | 642 | |
520 | 643 | //Fix preferences that fail validation, by replacing their value with default |
521 | | - foreach ( $prefsDescription['fields'] as $prefDescription ) { |
| 644 | + foreach ( $flattenedPrefs as $prefDescription ) { |
522 | 645 | $prefName = $prefDescription['name']; |
523 | 646 | if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) { |
524 | 647 | $prefs[$prefName] = $prefDescription['default']; |
— | — | @@ -568,28 +691,36 @@ |
569 | 692 | * @return Array: the messages needed by $prefsDescription. |
570 | 693 | */ |
571 | 694 | public static function getMessages( $prefsDescription ) { |
572 | | - $maybeMsgs = array(); |
| 695 | + $msgs = array(); |
573 | 696 | |
574 | | - if ( isset( $prefsDescription['intro'] ) ) { |
575 | | - $maybeMsgs[] = $prefsDescription['intro']; |
| 697 | + if ( isset( $prefsDescription['intro'] ) && self::isMessage( $prefsDescription['intro'] ) ) { |
| 698 | + $msgs[] = substr( $prefsDescription['intro'], 1 ); |
576 | 699 | } |
577 | 700 | |
578 | | - foreach ( $prefsDescription['fields'] as $prefName => $prefDesc ) { |
579 | | - $maybeMsgs[] = $prefDesc['label']; |
580 | | - |
581 | | - if ( $prefDesc['type'] == 'select' ) { |
582 | | - foreach ( $prefDesc['options'] as $optName => $value ) { |
583 | | - $maybeMsgs[] = $optName; |
| 701 | + foreach ( $prefsDescription['fields'] as $prefDesc ) { |
| 702 | + $type = $prefDesc['type']; |
| 703 | + $prefSpec = self::$prefsDescriptionSpecifications[$type]; |
| 704 | + if ( isset( $prefSpec['getMessages'] ) ) { |
| 705 | + $getMessages = $prefSpec['getMessages']; |
| 706 | + $msgs = array_merge( $msgs, call_user_func( $getMessages, $prefDesc ) ); |
| 707 | + } else { |
| 708 | + if ( isset( $prefDesc['label'] ) && self::isMessage( $prefDesc['label'] ) ) { |
| 709 | + $msgs[] = substr( $prefDesc['label'], 1 ); |
584 | 710 | } |
585 | 711 | } |
586 | 712 | } |
587 | 713 | |
| 714 | + return array_unique( $msgs ); |
| 715 | + } |
| 716 | + |
| 717 | + //Returns the messages for a 'select' field description |
| 718 | + private static function getSelectMessages( $prefDescription ) { |
588 | 719 | $msgs = array(); |
589 | | - foreach ( $maybeMsgs as $msg ) { |
590 | | - if ( self::isMessage( $msg ) ) { |
591 | | - $msgs[] = substr( $msg, 1 ); |
| 720 | + foreach ( $prefDescription['options'] as $optName => $value ) { |
| 721 | + if ( self::isMessage( $optName ) ) { |
| 722 | + $msgs[] = substr( $optName, 1 ); |
592 | 723 | } |
593 | 724 | } |
594 | | - return array_unique( $msgs ); |
| 725 | + return $msgs; |
595 | 726 | } |
596 | 727 | } |
Index: branches/salvatoreingala/Gadgets/api/ApiGetGadgetPrefs.php |
— | — | @@ -51,14 +51,9 @@ |
52 | 52 | if ( $userPrefs === null ) { |
53 | 53 | throw new MWException( __METHOD__ . ': $userPrefs should not be null.' ); |
54 | 54 | } |
55 | | - |
56 | | - //Add user preferences to preference description |
57 | | - foreach ( $prefsDescription['fields'] as $prefIdx => $prefDescription ) { |
58 | | - $prefName = $prefDescription['name']; |
59 | | - $prefsDescription['fields'][$prefIdx]['value'] = $userPrefs[$prefName]; |
60 | | - } |
61 | | - |
62 | | - $this->getResult()->addValue( null, $this->getModuleName(), $prefsDescription ); |
| 55 | + |
| 56 | + $this->getResult()->addValue( null, 'description', $prefsDescription ); |
| 57 | + $this->getResult()->addValue( null, 'values', $userPrefs ); |
63 | 58 | } |
64 | 59 | |
65 | 60 | public function getAllowedParams() { |
Index: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js |
— | — | @@ -87,9 +87,9 @@ |
88 | 88 | } |
89 | 89 | |
90 | 90 | //A field with no content |
91 | | - function EmptyField( $form, name, desc ) { |
| 91 | + function EmptyField( $form, desc, values ) { |
92 | 92 | //Check existence of compulsory fields |
93 | | - if ( typeof name == 'undefined' || !desc.type || !desc.label ) { |
| 93 | + if ( !desc.type || !desc.label ) { |
94 | 94 | $.error( "Missing arguments" ); |
95 | 95 | } |
96 | 96 | |
— | — | @@ -97,22 +97,16 @@ |
98 | 98 | |
99 | 99 | this.$p = $( '<p/>' ); |
100 | 100 | |
101 | | - this.name = name; |
102 | 101 | this.desc = desc; |
103 | 102 | } |
104 | 103 | |
105 | | - EmptyField.prototype.getName = function() { |
106 | | - return this.name; |
107 | | - }; |
108 | | - |
109 | 104 | EmptyField.prototype.getDesc = function() { |
110 | 105 | return this.desc; |
111 | 106 | }; |
112 | 107 | |
113 | | - |
114 | 108 | //Override expected |
115 | | - EmptyField.prototype.getValue = function() { |
116 | | - return null; |
| 109 | + EmptyField.prototype.getValues = function() { |
| 110 | + return {}; |
117 | 111 | }; |
118 | 112 | |
119 | 113 | EmptyField.prototype.getElement = function() { |
— | — | @@ -129,12 +123,12 @@ |
130 | 124 | //A field with just a label |
131 | 125 | LabelField.prototype = object( EmptyField.prototype ); |
132 | 126 | LabelField.prototype.constructor = LabelField; |
133 | | - function LabelField( $form, name, desc ) { |
134 | | - EmptyField.call( this, $form, name, desc ); |
| 127 | + function LabelField( $form, desc, values ) { |
| 128 | + EmptyField.call( this, $form, desc, values ); |
135 | 129 | |
136 | 130 | var $label = $( '<label/>' ) |
137 | 131 | .text( preproc( this.$form, this.desc.label ) ) |
138 | | - .attr('for', idPrefix + this.name ); |
| 132 | + .attr('for', idPrefix + this.desc.name ); |
139 | 133 | |
140 | 134 | this.$p.append( $label ); |
141 | 135 | } |
— | — | @@ -142,53 +136,59 @@ |
143 | 137 | //A field with a label and a checkbox |
144 | 138 | BooleanField.prototype = object( LabelField.prototype ); |
145 | 139 | BooleanField.prototype.constructor = BooleanField; |
146 | | - function BooleanField( $form, name, desc ){ |
147 | | - LabelField.call( this, $form, name, desc ); |
| 140 | + function BooleanField( $form, desc, values ){ |
| 141 | + LabelField.call( this, $form, desc, values ); |
148 | 142 | |
149 | | - if ( typeof desc.value != 'boolean' ) { |
150 | | - $.error( "desc.value is invalid" ); |
| 143 | + var value = values[this.desc.name]; |
| 144 | + if ( typeof value != 'boolean' ) { |
| 145 | + $.error( "value is invalid" ); |
151 | 146 | } |
152 | 147 | |
153 | 148 | this.$c = $( '<input/>' ) |
154 | 149 | .attr( 'type', 'checkbox' ) |
155 | | - .attr( 'id', idPrefix + this.name ) |
156 | | - .attr( 'name', idPrefix + this.name ) |
157 | | - .attr( 'checked', this.desc.value ); |
| 150 | + .attr( 'id', idPrefix + this.desc.name ) |
| 151 | + .attr( 'name', idPrefix + this.desc.name ) |
| 152 | + .attr( 'checked', value ); |
158 | 153 | |
159 | 154 | this.$p.append( this.$c ); |
160 | 155 | } |
161 | 156 | |
162 | | - BooleanField.prototype.getValue = function() { |
163 | | - return this.$c.is( ':checked' ); |
| 157 | + BooleanField.prototype.getValues = function() { |
| 158 | + var res = {}; |
| 159 | + res[this.desc.name] = this.$c.is( ':checked' ); |
| 160 | + return res; |
164 | 161 | }; |
165 | 162 | |
166 | 163 | //A field with a textbox accepting string values |
167 | 164 | |
168 | 165 | StringField.prototype = object( LabelField.prototype ); |
169 | 166 | StringField.prototype.constructor = StringField; |
170 | | - function StringField( $form, name, desc ){ |
171 | | - LabelField.call( this, $form, name, desc ); |
| 167 | + function StringField( $form, desc, values ){ |
| 168 | + LabelField.call( this, $form, desc, values ); |
172 | 169 | |
173 | | - if ( typeof desc.value != 'string' ) { |
174 | | - $.error( "desc.value is invalid" ); |
| 170 | + var value = values[this.desc.name]; |
| 171 | + if ( typeof value != 'string' ) { |
| 172 | + $.error( "value is invalid" ); |
175 | 173 | } |
176 | 174 | |
177 | 175 | this.$text = $( '<input/>' ) |
178 | 176 | .attr( 'type', 'text' ) |
179 | | - .attr( 'id', idPrefix + this.name ) |
180 | | - .attr( 'name', idPrefix + this.name ) |
181 | | - .val( desc.value ); |
| 177 | + .attr( 'id', idPrefix + this.desc.name ) |
| 178 | + .attr( 'name', idPrefix + this.desc.name ) |
| 179 | + .val( value ); |
182 | 180 | |
183 | 181 | this.$p.append( this.$text ); |
184 | 182 | } |
185 | 183 | |
186 | | - StringField.prototype.getValue = function() { |
187 | | - return this.$text.val(); |
| 184 | + StringField.prototype.getValues = function() { |
| 185 | + var res = {}; |
| 186 | + res[this.desc.name] = this.$text.val(); |
| 187 | + return res; |
188 | 188 | }; |
189 | 189 | |
190 | 190 | StringField.prototype.getValidationSettings = function() { |
191 | 191 | var settings = LabelField.prototype.getValidationSettings.call( this ), |
192 | | - fieldId = idPrefix + this.name; |
| 192 | + fieldId = idPrefix + this.desc.name; |
193 | 193 | |
194 | 194 | settings.rules[fieldId] = {}; |
195 | 195 | var fieldRules = settings.rules[fieldId], |
— | — | @@ -218,30 +218,33 @@ |
219 | 219 | //A field with a textbox accepting numeric values |
220 | 220 | NumberField.prototype = object( LabelField.prototype ); |
221 | 221 | NumberField.prototype.constructor = NumberField; |
222 | | - function NumberField( $form, name, desc ){ |
223 | | - LabelField.call( this, $form, name, desc ); |
| 222 | + function NumberField( $form, desc, values ){ |
| 223 | + LabelField.call( this, $form, desc, values ); |
224 | 224 | |
225 | | - if ( desc.value !== null && typeof desc.value != 'number' ) { |
226 | | - $.error( "desc.value is invalid" ); |
| 225 | + var value = values[this.desc.name]; |
| 226 | + if ( value !== null && typeof value != 'number' ) { |
| 227 | + $.error( "value is invalid" ); |
227 | 228 | } |
228 | 229 | |
229 | 230 | this.$text = $( '<input/>' ) |
230 | 231 | .attr( 'type', 'text' ) |
231 | | - .attr( 'id', idPrefix + this.name ) |
232 | | - .attr( 'name', idPrefix + this.name ) |
233 | | - .val( desc.value ); |
| 232 | + .attr( 'id', idPrefix + this.desc.name ) |
| 233 | + .attr( 'name', idPrefix + this.desc.name ) |
| 234 | + .val( value ); |
234 | 235 | |
235 | 236 | this.$p.append( this.$text ); |
236 | 237 | } |
237 | 238 | |
238 | | - NumberField.prototype.getValue = function() { |
239 | | - var val = parseFloat( this.$text.val() ); |
240 | | - return isNaN( val ) ? null : val; |
| 239 | + NumberField.prototype.getValues = function() { |
| 240 | + var val = parseFloat( this.$text.val() ), |
| 241 | + res = {}; |
| 242 | + res[this.desc.name] = isNaN( val ) ? null : val; |
| 243 | + return res; |
241 | 244 | }; |
242 | 245 | |
243 | 246 | NumberField.prototype.getValidationSettings = function() { |
244 | 247 | var settings = LabelField.prototype.getValidationSettings.call( this ), |
245 | | - fieldId = idPrefix + this.name; |
| 248 | + fieldId = idPrefix + this.desc.name; |
246 | 249 | |
247 | 250 | settings.rules[fieldId] = {}; |
248 | 251 | var fieldRules = settings.rules[fieldId], |
— | — | @@ -277,50 +280,54 @@ |
278 | 281 | //A field with a drop-down list |
279 | 282 | SelectField.prototype = object( LabelField.prototype ); |
280 | 283 | SelectField.prototype.constructor = SelectField; |
281 | | - function SelectField( $form, name, desc ){ |
282 | | - LabelField.call( this, $form, name, desc ); |
| 284 | + function SelectField( $form, desc, values ){ |
| 285 | + LabelField.call( this, $form, desc, values ); |
283 | 286 | |
284 | 287 | var $select = this.$select = $( '<select/>' ) |
285 | | - .attr( 'id', idPrefix + this.name ) |
286 | | - .attr( 'name', idPrefix + this.name ); |
| 288 | + .attr( 'id', idPrefix + this.desc.name ) |
| 289 | + .attr( 'name', idPrefix + this.desc.name ); |
287 | 290 | |
288 | | - var values = []; |
| 291 | + var validValues = []; |
289 | 292 | var self = this; |
290 | 293 | $.each( desc.options, function( optName, optVal ) { |
291 | | - var i = values.length; |
| 294 | + var i = validValues.length; |
292 | 295 | $( '<option/>' ) |
293 | 296 | .text( preproc( self.$form, optName ) ) |
294 | 297 | .val( i ) |
295 | 298 | .appendTo( $select ); |
296 | | - values.push( optVal ); |
| 299 | + validValues.push( optVal ); |
297 | 300 | } ); |
298 | 301 | |
299 | | - this.values = values; |
| 302 | + this.validValues = validValues; |
300 | 303 | |
301 | | - if ( $.inArray( desc.value, values ) == -1 ) { |
302 | | - $.error( "desc.value is not in the list of possible values" ); |
| 304 | + var value = values[this.desc.name]; |
| 305 | + if ( $.inArray( value, validValues ) == -1 ) { |
| 306 | + $.error( "value is not in the list of possible values" ); |
303 | 307 | } |
304 | 308 | |
305 | | - var i = $.inArray( desc.value, values ); |
| 309 | + var i = $.inArray( value, validValues ); |
306 | 310 | $select.val( i ).attr( 'selected', 'selected' ); |
307 | 311 | |
308 | 312 | this.$p.append( $select ); |
309 | 313 | } |
310 | 314 | |
311 | | - SelectField.prototype.getValue = function() { |
312 | | - var i = parseInt( this.$select.val(), 10 ); |
313 | | - return this.values[i]; |
| 315 | + SelectField.prototype.getValues = function() { |
| 316 | + var i = parseInt( this.$select.val(), 10 ), |
| 317 | + res = {}; |
| 318 | + res[this.desc.name] = this.validValues[i]; |
| 319 | + return res; |
314 | 320 | }; |
315 | 321 | |
316 | 322 | |
317 | 323 | //A field with a slider, representing ranges of numbers |
318 | 324 | RangeField.prototype = object( LabelField.prototype ); |
319 | 325 | RangeField.prototype.constructor = RangeField; |
320 | | - function RangeField( $form, name, desc ){ |
321 | | - LabelField.call( this, $form, name, desc ); |
| 326 | + function RangeField( $form, desc, values ){ |
| 327 | + LabelField.call( this, $form, desc, values ); |
322 | 328 | |
323 | | - if ( typeof desc.value != 'number' ) { |
324 | | - $.error( "desc.value is invalid" ); |
| 329 | + var value = values[this.desc.name]; |
| 330 | + if ( typeof value != 'number' ) { |
| 331 | + $.error( "value is invalid" ); |
325 | 332 | } |
326 | 333 | |
327 | 334 | if ( typeof desc.min != 'number' ) { |
— | — | @@ -335,17 +342,17 @@ |
336 | 343 | $.error( "desc.step is invalid" ); |
337 | 344 | } |
338 | 345 | |
339 | | - if ( desc.value < desc.min || desc.value > desc.max ) { |
340 | | - $.error( "desc.value is out of range" ); |
| 346 | + if ( value < desc.min || value > desc.max ) { |
| 347 | + $.error( "value is out of range" ); |
341 | 348 | } |
342 | 349 | |
343 | 350 | var $slider = this.$slider = $( '<div/>' ) |
344 | | - .attr( 'id', idPrefix + this.name ); |
| 351 | + .attr( 'id', idPrefix + this.desc.name ); |
345 | 352 | |
346 | 353 | var options = { |
347 | 354 | min: desc.min, |
348 | 355 | max: desc.max, |
349 | | - value: desc.value |
| 356 | + value: value |
350 | 357 | }; |
351 | 358 | |
352 | 359 | if ( typeof desc.step != 'undefined' ) { |
— | — | @@ -357,34 +364,37 @@ |
358 | 365 | this.$p.append( $slider ); |
359 | 366 | } |
360 | 367 | |
361 | | - RangeField.prototype.getValue = function() { |
362 | | - return this.$slider.slider( 'value' ); |
| 368 | + RangeField.prototype.getValues = function() { |
| 369 | + var res = {}; |
| 370 | + res[this.desc.name] = this.$slider.slider( 'value' ); |
| 371 | + return res; |
363 | 372 | }; |
364 | 373 | |
365 | 374 | |
366 | 375 | //A field with a textbox with a datepicker |
367 | 376 | DateField.prototype = object( LabelField.prototype ); |
368 | 377 | DateField.prototype.constructor = DateField; |
369 | | - function DateField( $form, name, desc ){ |
370 | | - LabelField.call( this, $form, name, desc ); |
| 378 | + function DateField( $form, desc, values ){ |
| 379 | + LabelField.call( this, $form, desc, values ); |
371 | 380 | |
372 | | - if ( typeof desc.value == 'undefined' ) { |
373 | | - $.error( "desc.value is invalid" ); |
| 381 | + var value = values[this.desc.name]; |
| 382 | + if ( typeof value == 'undefined' ) { |
| 383 | + $.error( "value is invalid" ); |
374 | 384 | } |
375 | 385 | |
376 | 386 | var date; |
377 | | - if ( desc.value !== null ) { |
378 | | - date = new Date( desc.value ); |
| 387 | + if ( value !== null ) { |
| 388 | + date = new Date( value ); |
379 | 389 | |
380 | 390 | if ( !isFinite( date ) ) { |
381 | | - $.error( "desc.value is invalid" ); |
| 391 | + $.error( "value is invalid" ); |
382 | 392 | } |
383 | 393 | } |
384 | 394 | |
385 | 395 | this.$text = $( '<input/>' ) |
386 | 396 | .attr( 'type', 'text' ) |
387 | | - .attr( 'id', idPrefix + this.name ) |
388 | | - .attr( 'name', idPrefix + this.name ) |
| 397 | + .attr( 'id', idPrefix + this.desc.name ) |
| 398 | + .attr( 'name', idPrefix + this.desc.name ) |
389 | 399 | .datepicker( { |
390 | 400 | onSelect: function() { |
391 | 401 | //Force validation, so that a previous 'invalid' state is removed |
— | — | @@ -392,7 +402,7 @@ |
393 | 403 | } |
394 | 404 | } ); |
395 | 405 | |
396 | | - if ( desc.value !== null ) { |
| 406 | + if ( value !== null ) { |
397 | 407 | this.$text.datepicker( 'setDate', date ); |
398 | 408 | } |
399 | 409 | |
— | — | @@ -400,25 +410,28 @@ |
401 | 411 | this.$p.append( this.$text ); |
402 | 412 | } |
403 | 413 | |
404 | | - DateField.prototype.getValue = function() { |
405 | | - var d = this.$text.datepicker( 'getDate' ); |
| 414 | + DateField.prototype.getValues = function() { |
| 415 | + var d = this.$text.datepicker( 'getDate' ), |
| 416 | + res = {}; |
406 | 417 | |
407 | 418 | if ( d === null ) { |
408 | 419 | return null; |
409 | 420 | } |
410 | 421 | |
411 | 422 | //UTC date in ISO 8601 format [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss]Z |
412 | | - return '' + pad( d.getUTCFullYear(), 4 ) + '-' + |
| 423 | + res[this.desc.name] = '' + |
| 424 | + pad( d.getUTCFullYear(), 4 ) + '-' + |
413 | 425 | pad( d.getUTCMonth() + 1, 2 ) + '-' + |
414 | 426 | pad( d.getUTCDate(), 2 ) + 'T' + |
415 | 427 | pad( d.getUTCHours(), 2 ) + ':' + |
416 | 428 | pad( d.getUTCMinutes(), 2 ) + ':' + |
417 | 429 | pad( d.getUTCSeconds(), 2 ) + 'Z'; |
| 430 | + return res; |
418 | 431 | }; |
419 | 432 | |
420 | 433 | DateField.prototype.getValidationSettings = function() { |
421 | 434 | var settings = LabelField.prototype.getValidationSettings.call( this ), |
422 | | - fieldId = idPrefix + this.name; |
| 435 | + fieldId = idPrefix + this.desc.name; |
423 | 436 | |
424 | 437 | settings.rules[fieldId] = { |
425 | 438 | "datePicker": true |
— | — | @@ -436,20 +449,21 @@ |
437 | 450 | |
438 | 451 | ColorField.prototype = object( LabelField.prototype ); |
439 | 452 | ColorField.prototype.constructor = ColorField; |
440 | | - function ColorField( $form, name, desc ){ |
441 | | - LabelField.call( this, $form, name, desc ); |
| 453 | + function ColorField( $form, desc, values ){ |
| 454 | + LabelField.call( this, $form, desc, values ); |
442 | 455 | |
443 | | - if ( typeof desc.value == 'undefined' ) { |
444 | | - $.error( "desc.value is invalid" ); |
| 456 | + var value = values[this.desc.name]; |
| 457 | + if ( typeof value == 'undefined' ) { |
| 458 | + $.error( "value is invalid" ); |
445 | 459 | } |
446 | 460 | |
447 | 461 | this.$text = $( '<input/>' ) |
448 | 462 | .attr( 'type', 'text' ) |
449 | | - .attr( 'id', idPrefix + this.name ) |
450 | | - .attr( 'name', idPrefix + this.name ) |
| 463 | + .attr( 'id', idPrefix + this.desc.name ) |
| 464 | + .attr( 'name', idPrefix + this.desc.name ) |
451 | 465 | .addClass( 'colorpicker-input' ) |
452 | | - .val( desc.value ) |
453 | | - .css( 'background-color', desc.value ) |
| 466 | + .val( value ) |
| 467 | + .css( 'background-color', value ) |
454 | 468 | .focus( function() { |
455 | 469 | $( '<div/>' ) |
456 | 470 | .attr( 'id', 'colorpicker' ) |
— | — | @@ -484,7 +498,7 @@ |
485 | 499 | |
486 | 500 | ColorField.prototype.getValidationSettings = function() { |
487 | 501 | var settings = LabelField.prototype.getValidationSettings.call( this ), |
488 | | - fieldId = idPrefix + this.name; |
| 502 | + fieldId = idPrefix + this.desc.name; |
489 | 503 | |
490 | 504 | settings.rules[fieldId] = { |
491 | 505 | "color": true |
— | — | @@ -492,10 +506,12 @@ |
493 | 507 | return settings; |
494 | 508 | } |
495 | 509 | |
496 | | - ColorField.prototype.getValue = function() { |
497 | | - var color = $.colorUtil.getRGB( this.$text.val() ); |
498 | | - return '#' + pad( color[0].toString( 16 ), 2 ) + |
| 510 | + ColorField.prototype.getValues = function() { |
| 511 | + var color = $.colorUtil.getRGB( this.$text.val() ), |
| 512 | + res = {} |
| 513 | + res[this.desc.name] = '#' + pad( color[0].toString( 16 ), 2 ) + |
499 | 514 | pad( color[1].toString( 16 ), 2 ) + pad( color[2].toString( 16 ), 2 ); |
| 515 | + return res; |
500 | 516 | }; |
501 | 517 | |
502 | 518 | //If a click happens outside the colorpicker while it is showed, remove it |
— | — | @@ -559,7 +575,6 @@ |
560 | 576 | for ( var i = 0; i < description.fields.length; i++ ) { |
561 | 577 | //TODO: validate fieldName |
562 | 578 | var field = description.fields[i], |
563 | | - fieldName = field.name, |
564 | 579 | FieldConstructor = validFieldTypes[field.type]; |
565 | 580 | |
566 | 581 | if ( typeof FieldConstructor != 'function' ) { |
— | — | @@ -569,7 +584,7 @@ |
570 | 585 | |
571 | 586 | var f; |
572 | 587 | try { |
573 | | - f = new FieldConstructor( $form, fieldName, field ); |
| 588 | + f = new FieldConstructor( $form, field, options.values ); |
574 | 589 | } catch ( e ) { |
575 | 590 | mw.log( e ); |
576 | 591 | return null; //constructor failed, wrong syntax in field description |
— | — | @@ -610,7 +625,7 @@ |
611 | 626 | |
612 | 627 | for ( var i = 0; i < data.fields.length; i++ ) { |
613 | 628 | var f = data.fields[i]; |
614 | | - result[f.getName()] = f.getValue(); |
| 629 | + $.extend( result, f.getValues() ); |
615 | 630 | } |
616 | 631 | |
617 | 632 | return result; |
Index: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js |
— | — | @@ -57,17 +57,21 @@ |
58 | 58 | dataType: "json", // response type |
59 | 59 | success: function( response ) { |
60 | 60 | |
61 | | - if ( typeof response.getgadgetprefs != 'object' ) { |
| 61 | + if ( typeof response.description != 'object' |
| 62 | + || typeof response.values != 'object') |
| 63 | + { |
62 | 64 | alert( mw.msg( 'gadgets-unexpected-error' ) ) |
63 | 65 | return; |
64 | 66 | } |
65 | 67 | |
66 | 68 | //Create and show dialog |
67 | 69 | |
68 | | - var prefs = response.getgadgetprefs; |
| 70 | + var prefsDescription = response.description; |
| 71 | + var values = response.values; |
69 | 72 | |
70 | | - var dialogBody = $( prefs ).formBuilder( { |
71 | | - gadget: gadget |
| 73 | + var dialogBody = $( prefsDescription ).formBuilder( { |
| 74 | + gadget: gadget, |
| 75 | + values: values |
72 | 76 | } ); |
73 | 77 | |
74 | 78 | $( dialogBody ).submit( function() { |