r90469 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r90468‎ | r90469 | r90470 >
Date:16:42, 20 June 2011
Author:salvatoreingala
Status:deferred
Tags:
Comment:
Splitted Gadgets_body.php, changed folder structure.
Modified paths:
  • /branches/salvatoreingala/Gadgets/ApiGetGadgetPrefs.php (deleted) (history)
  • /branches/salvatoreingala/Gadgets/ApiQueryGadgetCategories.php (deleted) (history)
  • /branches/salvatoreingala/Gadgets/ApiQueryGadgets.php (deleted) (history)
  • /branches/salvatoreingala/Gadgets/ApiSetGadgetPrefs.php (deleted) (history)
  • /branches/salvatoreingala/Gadgets/Gadgets.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/Gadgets_body.php (deleted) (history)
  • /branches/salvatoreingala/Gadgets/SpecialGadgets.php (deleted) (history)
  • /branches/salvatoreingala/Gadgets/api (added) (history)
  • /branches/salvatoreingala/Gadgets/api/ApiGetGadgetPrefs.php (added) (history)
  • /branches/salvatoreingala/Gadgets/api/ApiQueryGadgetCategories.php (added) (history)
  • /branches/salvatoreingala/Gadgets/api/ApiQueryGadgets.php (added) (history)
  • /branches/salvatoreingala/Gadgets/api/ApiSetGadgetPrefs.php (added) (history)
  • /branches/salvatoreingala/Gadgets/backend (added) (history)
  • /branches/salvatoreingala/Gadgets/backend/Gadget.php (added) (history)
  • /branches/salvatoreingala/Gadgets/backend/GadgetHooks.php (added) (history)
  • /branches/salvatoreingala/Gadgets/backend/GadgetResourceLoaderModule.php (added) (history)
  • /branches/salvatoreingala/Gadgets/modules (deleted) (history)
  • /branches/salvatoreingala/Gadgets/ui (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/GadgetsMainModule.php (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/SpecialGadgets.php (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.css (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js (added) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources/jquery.validate.js (added) (history)

Diff [purge]

Index: branches/salvatoreingala/Gadgets/Gadgets_body.php
@@ -1,1182 +0,0 @@
2 -<?php
3 -/**
4 - * Gadgets extension - lets users select custom javascript gadgets
5 - *
6 - *
7 - * For more info see http://mediawiki.org/wiki/Extension:Gadgets
8 - *
9 - * @file
10 - * @ingroup Extensions
11 - * @author Daniel Kinzler, brightbyte.de
12 - * @copyright © 2007 Daniel Kinzler
13 - * @license GNU General Public Licence 2.0 or later
14 - */
15 -
16 -class GadgetHooks {
17 -
18 - /**
19 - * ArticleSaveComplete hook handler.
20 - *
21 - * @param $article Article
22 - * @param $user User
23 - * @param $text String: New page text
24 - */
25 - public static function articleSaveComplete( $article, $user, $text ) {
26 - //update cache if MediaWiki:Gadgets-definition was edited
27 - $title = $article->mTitle;
28 - if( $title->getNamespace() == NS_MEDIAWIKI && $title->getText() == 'Gadgets-definition' ) {
29 - Gadget::loadStructuredList( $text );
30 - }
31 - return true;
32 - }
33 -
34 - /**
35 - * GetPreferences hook handler.
36 - * @param $user User
37 - * @param $preferences Array: Preference descriptions
38 - */
39 - public static function getPreferences( $user, &$preferences ) {
40 - $gadgets = Gadget::loadStructuredList();
41 - if (!$gadgets) return true;
42 -
43 - $options = array();
44 - $default = array();
45 - foreach( $gadgets as $section => $thisSection ) {
46 - $available = array();
47 - foreach( $thisSection as $gadget ) {
48 - if ( $gadget->isAllowed( $user ) ) {
49 - $gname = $gadget->getName();
50 - $available[$gadget->getDescription()] = $gname;
51 - if ( $gadget->isEnabled( $user ) ) {
52 - $default[] = $gname;
53 - }
54 - }
55 - }
56 - if ( $section !== '' ) {
57 - $section = wfMsgExt( "gadget-section-$section", 'parseinline' );
58 - if ( count ( $available ) ) {
59 - $options[$section] = $available;
60 - }
61 - } else {
62 - $options = array_merge( $options, $available );
63 - }
64 - }
65 -
66 - $preferences['gadgets-intro'] =
67 - array(
68 - 'type' => 'info',
69 - 'label' => '&#160;',
70 - 'default' => Xml::tags( 'tr', array(),
71 - Xml::tags( 'td', array( 'colspan' => 2 ),
72 - wfMsgExt( 'gadgets-prefstext', 'parse' ) ) ),
73 - 'section' => 'gadgets',
74 - 'raw' => 1,
75 - 'rawrow' => 1,
76 - );
77 -
78 - $preferences['gadgets'] =
79 - array(
80 - 'type' => 'multiselect',
81 - 'options' => $options,
82 - 'section' => 'gadgets',
83 - 'label' => '&#160;',
84 - 'prefix' => 'gadget-',
85 - 'default' => $default,
86 - );
87 -
88 - return true;
89 - }
90 -
91 - /**
92 - * ResourceLoaderRegisterModules hook handler.
93 - * @param $resourceLoader ResourceLoader
94 - */
95 - public static function registerModules( &$resourceLoader ) {
96 - $gadgets = Gadget::loadList();
97 - if ( !$gadgets ) {
98 - return true;
99 - }
100 - foreach ( $gadgets as $g ) {
101 - $module = $g->getModule();
102 - if ( $module ) {
103 - $resourceLoader->register( $g->getModuleName(), $module );
104 - }
105 - }
106 - return true;
107 - }
108 -
109 - /**
110 - * BeforePageDisplay hook handler.
111 - * @param $out OutputPage
112 - */
113 - public static function beforePageDisplay( $out ) {
114 - global $wgUser;
115 -
116 - wfProfileIn( __METHOD__ );
117 -
118 - //tweaks in Special:Preferences
119 - if ( $out->getTitle()->isSpecial( 'Preferences' ) ) {
120 - $out->addModules( 'ext.gadgets.preferences' );
121 - }
122 -
123 - $gadgets = Gadget::loadList();
124 - if ( !$gadgets ) {
125 - wfProfileOut( __METHOD__ );
126 - return true;
127 - }
128 -
129 - $lb = new LinkBatch();
130 - $lb->setCaller( __METHOD__ );
131 - $pages = array();
132 -
133 - foreach ( $gadgets as $gadget ) {
134 - if ( $gadget->isEnabled( $wgUser ) && $gadget->isAllowed( $wgUser ) ) {
135 - if ( $gadget->hasModule() ) {
136 - $out->addModules( $gadget->getModuleName() );
137 - }
138 - foreach ( $gadget->getLegacyScripts() as $page ) {
139 - $lb->add( NS_MEDIAWIKI, $page );
140 - $pages[] = $page;
141 - }
142 - }
143 - }
144 -
145 - $lb->execute( __METHOD__ );
146 -
147 - $done = array();
148 - foreach ( $pages as $page ) {
149 - if ( isset( $done[$page] ) ) continue;
150 - $done[$page] = true;
151 - self::applyScript( $page, $out );
152 - }
153 - wfProfileOut( __METHOD__ );
154 -
155 - return true;
156 - }
157 -
158 - /**
159 - * UserLoadOptions hook handler.
160 - * @param $user
161 - * @param &$options
162 - */
163 - public static function userLoadOptions( $user, &$options ) {
164 - //Only if it's current user
165 - $curUser = RequestContext::getMain()->getUser();
166 - if ( $curUser->getID() !== $user->getID() ) {
167 - return true;
168 - }
169 -
170 - //Find out all existing gadget preferences and save them in a map
171 - $preferencesCache = array();
172 - foreach ( $options as $option => $value ) {
173 - $m = array();
174 - if ( preg_match( '/gadget-([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)-config/', $option, $m ) ) {
175 - $gadgetName = $m[1];
176 - wfSuppressWarnings();
177 - $gadgetPrefs = unserialize( $value );
178 - wfRestoreWarnings();
179 - if ( $gadgetPrefs !== false ) {
180 - $preferencesCache[$gadgetName] = $gadgetPrefs;
181 - } else {
182 - //should not happen; just in case
183 - wfDebug( __METHOD__ . ": couldn't unserialize settings for gadget " .
184 - "$gadgetName and user {$curUser->getID()}. Ignoring.\n" );
185 - }
186 - unset( $options[$option] );
187 - }
188 - }
189 -
190 - //Record preferences for each gadget
191 - $gadgets = Gadget::loadList();
192 - foreach ( $gadgets as $gadget ) {
193 - $prefsDescription = $gadget->getPrefsDescription();
194 - if ( $prefsDescription !== null ) {
195 - if ( isset( $preferencesCache[$gadget->getName()] ) ) {
196 - $userPrefs = $preferencesCache[$gadget->getName()];
197 - }
198 -
199 - if ( !isset( $userPrefs ) ) {
200 - $userPrefs = array(); //no saved prefs (or invalid entry in DB), use defaults
201 - }
202 -
203 - Gadget::matchPrefsWithDescription( $prefsDescription, $userPrefs );
204 -
205 - $gadget->setPrefs( $userPrefs );
206 - }
207 - }
208 -
209 - return true;
210 - }
211 -
212 - /**
213 - * UserSaveOptions hook handler.
214 - * @param $user
215 - * @param &$options
216 - */
217 - public static function userSaveOptions( $user, &$options ) {
218 - //Only if it's current user
219 - $curUser = RequestContext::getMain()->getUser();
220 - if ( $curUser->getID() !== $user->getID() ) {
221 - return true;
222 - }
223 -
224 - //Reinsert gadget-*-config options, so they can be saved back
225 - $gadgets = Gadget::loadList();
226 -
227 - if ( !$gadgets ) {
228 - return true;
229 - }
230 -
231 - foreach ( $gadgets as $gadget ) {
232 - if ( $gadget->getPrefs() !== null ) {
233 - //TODO: should remove prefs that equal their default
234 -
235 - $prefsSerialized = serialize( $gadget->getPrefs() );
236 - $options["gadget-{$gadget->getName()}-config"] = $prefsSerialized;
237 - }
238 - }
239 -
240 - return true;
241 - }
242 -
243 - /**
244 - * Adds one legacy script to output.
245 - *
246 - * @param $page String: Unprefixed page title
247 - * @param $out OutputPage
248 - */
249 - private static function applyScript( $page, $out ) {
250 - global $wgJsMimeType;
251 -
252 - # bug 22929: disable gadgets on sensitive pages. Scripts loaded through the
253 - # ResourceLoader handle this in OutputPage::getModules()
254 - # TODO: make this extension load everything via RL, then we don't need to worry
255 - # about any of this.
256 - if( $out->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) < ResourceLoaderModule::ORIGIN_USER_SITEWIDE ){
257 - return;
258 - }
259 -
260 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, $page );
261 - if ( !$t ) return;
262 -
263 - $u = $t->getLocalURL( 'action=raw&ctype=' . $wgJsMimeType );
264 - $out->addScriptFile( $u, $t->getLatestRevID() );
265 - }
266 -
267 - /**
268 - * UnitTestsList hook handler
269 - * @param $files Array: List of extension test files
270 - */
271 - public static function unitTestsList( $files ) {
272 - $files[] = dirname( __FILE__ ) . '/Gadgets_tests.php';
273 - return true;
274 - }
275 -}
276 -
277 -
278 -/**
279 - * Wrapper for one gadget.
280 - */
281 -class Gadget {
282 - /**
283 - * Increment this when changing class structure
284 - */
285 - const GADGET_CLASS_VERSION = 5;
286 -
287 - private $version = self::GADGET_CLASS_VERSION,
288 - $scripts = array(),
289 - $styles = array(),
290 - $dependencies = array(),
291 - $name,
292 - $definition,
293 - $resourceLoaded = false,
294 - $requiredRights = array(),
295 - $onByDefault = false,
296 - $category,
297 - $preferences = null;
298 -
299 -
300 - //Syntax specifications of preference description language
301 - private static $prefsDescriptionSpecifications = array(
302 - 'boolean' => array(
303 - 'default' => array(
304 - 'isMandatory' => true,
305 - 'checker' => 'is_bool'
306 - ),
307 - 'label' => array(
308 - 'isMandatory' => true,
309 - 'checker' => 'is_string'
310 - )
311 - ),
312 - 'string' => array(
313 - 'default' => array(
314 - 'isMandatory' => true,
315 - 'checker' => 'is_string'
316 - ),
317 - 'label' => array(
318 - 'isMandatory' => true,
319 - 'checker' => 'is_string'
320 - ),
321 - 'required' => array(
322 - 'isMandatory' => false,
323 - 'checker' => 'is_bool'
324 - ),
325 - 'minlength' => array(
326 - 'isMandatory' => false,
327 - 'checker' => 'is_integer'
328 - ),
329 - 'maxlength' => array(
330 - 'isMandatory' => false,
331 - 'checker' => 'is_integer'
332 - )
333 - ),
334 - 'number' => array(
335 - 'default' => array(
336 - 'isMandatory' => true,
337 - 'checker' => 'Gadget::isFloatOrInt'
338 - ),
339 - 'label' => array(
340 - 'isMandatory' => true,
341 - 'checker' => 'is_string'
342 - ),
343 - 'required' => array(
344 - 'isMandatory' => false,
345 - 'checker' => 'is_bool'
346 - ),
347 - 'integer' => array(
348 - 'isMandatory' => false,
349 - 'checker' => 'is_bool'
350 - ),
351 - 'min' => array(
352 - 'isMandatory' => false,
353 - 'checker' => 'Gadget::isFloatOrInt'
354 - ),
355 - 'max' => array(
356 - 'isMandatory' => false,
357 - 'checker' => 'Gadget::isFloatOrInt'
358 - )
359 - ),
360 - 'select' => array(
361 - 'default' => array(
362 - 'isMandatory' => true
363 - ),
364 - 'label' => array(
365 - 'isMandatory' => true,
366 - 'checker' => 'is_string'
367 - ),
368 - 'options' => array(
369 - 'isMandatory' => true,
370 - 'checker' => 'is_array'
371 - )
372 - )
373 - );
374 -
375 - //Type-specific checkers for finer validation
376 - private static $typeCheckers = array(
377 - 'string' => 'Gadget::checkStringOptionDefinition',
378 - 'number' => 'Gadget::checkNumberOptionDefinition',
379 - 'select' => 'Gadget::checkSelectOptionDefinition'
380 - );
381 -
382 - //Further checks for 'string' options
383 - private static function checkStringOptionDefinition( $option ) {
384 - if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) {
385 - return false;
386 - }
387 -
388 - if ( isset( $option['maxlength'] ) && $option['maxlength'] <= 0 ) {
389 - return false;
390 - }
391 -
392 - if ( isset( $option['minlength']) && isset( $option['maxlength'] ) ) {
393 - if ( $option['minlength'] > $option['maxlength'] ) {
394 - return false;
395 - }
396 - }
397 -
398 - //$default must pass validation, too
399 - $required = isset( $option['required'] ) ? $option['required'] : true;
400 - $default = $option['default'];
401 -
402 - if ( $required === false && $default === '' ) {
403 - return true; //empty string is good, skip other checks
404 - }
405 -
406 - $len = strlen( $default );
407 - if ( isset( $option['minlength'] ) && $len < $option['minlength'] ) {
408 - return false;
409 - }
410 - if ( isset( $option['maxlength'] ) && $len > $option['maxlength'] ) {
411 - return false;
412 - }
413 -
414 - return true;
415 - }
416 -
417 - private static function isFloatOrInt( $param ) {
418 - return is_float( $param ) || is_int( $param );
419 - }
420 -
421 - //Further checks for 'number' options
422 - private static function checkNumberOptionDefinition( $option ) {
423 - if ( isset( $option['integer'] ) && $option['integer'] === true ) {
424 - //Check if 'min', 'max' and 'default' are integers (if given)
425 - if ( intval( $option['default'] ) != $option['default'] ) {
426 - return false;
427 - }
428 - if ( isset( $option['min'] ) && intval( $option['min'] ) != $option['min'] ) {
429 - return false;
430 - }
431 - if ( isset( $option['max'] ) && intval( $option['max'] ) != $option['max'] ) {
432 - return false;
433 - }
434 - }
435 -
436 - //validate $option['default']
437 - $default = $option['default'];
438 -
439 - if ( isset( $option['min'] ) && $default < $option['min'] ) {
440 - return false;
441 - }
442 - if ( isset( $option['max'] ) && $default > $option['max'] ) {
443 - return false;
444 - }
445 -
446 - return true;
447 - }
448 -
449 - private static function checkSelectOptionDefinition( $option ) {
450 - $options = $option['options'];
451 -
452 - foreach ( $options as $opt => $optVal ) {
453 - //Correct value for $optVal are NULL, boolean, integer, float or string
454 - if ( $optVal !== NULL &&
455 - !is_bool( $optVal ) &&
456 - !is_int( $optVal ) &&
457 - !is_float( $optVal ) &&
458 - !is_string( $optVal ) )
459 - {
460 - return false;
461 - }
462 - }
463 -
464 - $values = array_values( $options );
465 -
466 - $default = $option['default'];
467 -
468 - //Checks that $default is one of the option values
469 - if ( !in_array( $default, $values, true ) ){
470 - return false;
471 - }
472 -
473 - return true;
474 - }
475 -
476 - /**
477 - * Creates an instance of this class from definition in MediaWiki:Gadgets-definition
478 - * @param $definition String: Gadget definition
479 - * @return Mixed: Instance of Gadget class or false if $definition is invalid
480 - */
481 - public static function newFromDefinition( $definition ) {
482 - $m = array();
483 - if ( !preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/', $definition, $m ) ) {
484 - return false;
485 - }
486 - //NOTE: the gadget name is used as part of the name of a form field,
487 - // and must follow the rules defined in http://www.w3.org/TR/html4/types.html#type-cdata
488 - // Also, title-normalization applies.
489 - $gadget = new Gadget();
490 - $gadget->name = trim( str_replace(' ', '_', $m[1] ) );
491 - $gadget->definition = $definition;
492 - $options = trim( $m[2], ' []' );
493 - foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) {
494 - $arr = preg_split( '/\s*=\s*/', $option, 2 );
495 - $option = $arr[0];
496 - if ( isset( $arr[1] ) ) {
497 - $params = explode( ',', $arr[1] );
498 - $params = array_map( 'trim', $params );
499 - } else {
500 - $params = array();
501 - }
502 - switch ( $option ) {
503 - case 'ResourceLoader':
504 - $gadget->resourceLoaded = true;
505 - break;
506 - case 'dependencies':
507 - $gadget->dependencies = $params;
508 - break;
509 - case 'rights':
510 - $gadget->requiredRights = $params;
511 - break;
512 - case 'default':
513 - $gadget->onByDefault = true;
514 - break;
515 - }
516 - }
517 - foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
518 - $page = "Gadget-$page";
519 - if ( preg_match( '/\.js/', $page ) ) {
520 - $gadget->scripts[] = $page;
521 - } elseif ( preg_match( '/\.css/', $page ) ) {
522 - $gadget->styles[] = $page;
523 - }
524 - }
525 - return $gadget;
526 - }
527 -
528 - /**
529 - * @return String: Gadget name
530 - */
531 - public function getName() {
532 - return $this->name;
533 - }
534 -
535 - /**
536 - * @return String: Gadget description parsed into HTML
537 - */
538 - public function getDescription() {
539 - return wfMessage( "gadget-{$this->getName()}" )->parse();
540 - }
541 -
542 - /**
543 - * @return String: Wikitext of gadget description
544 - */
545 - public function getRawDescription() {
546 - return wfMessage( "gadget-{$this->getName()}" )->plain();
547 - }
548 -
549 - /**
550 - * @return String: Name of category (aka section) our gadget belongs to. Empty string if none.
551 - */
552 - public function getCategory() {
553 - return $this->category;
554 - }
555 -
556 - /**
557 - * @return String: Name of ResourceLoader module for this gadget
558 - */
559 - public function getModuleName() {
560 - return "ext.gadget.{$this->name}";
561 - }
562 -
563 - /**
564 - * Checks whether this is an instance of an older version of this class deserialized from cache
565 - * @return Boolean
566 - */
567 - public function isOutdated() {
568 - return $this->version != self::GADGET_CLASS_VERSION;
569 - }
570 -
571 - /**
572 - * Checks whether this gadget is enabled for given user
573 - *
574 - * @param $user User: user to check against
575 - * @return Boolean
576 - */
577 - public function isEnabled( $user ) {
578 - return (bool)$user->getOption( "gadget-{$this->name}", $this->onByDefault );
579 - }
580 -
581 - /**
582 - * Checks whether given user has permissions to use this gadget
583 - *
584 - * @param $user User: user to check against
585 - * @return Boolean
586 - */
587 - public function isAllowed( $user ) {
588 - return count( array_intersect( $this->requiredRights, $user->getRights() ) ) == count( $this->requiredRights );
589 - }
590 -
591 - /**
592 - * @return Boolean: Whether this gadget is on by default for everyone (but can be disabled in preferences)
593 - */
594 - public function isOnByDefault() {
595 - return $this->onByDefault;
596 - }
597 -
598 - /**
599 - * @return Boolean: Whether all of this gadget's JS components support ResourceLoader
600 - */
601 - public function supportsResourceLoader() {
602 - return $this->resourceLoaded;
603 - }
604 -
605 - /**
606 - * @return Boolean: Whether this gadget has resources that can be loaded via ResourceLoader
607 - */
608 - public function hasModule() {
609 - return count( $this->styles )
610 - + ( $this->supportsResourceLoader() ? count( $this->scripts ) : 0 )
611 - > 0;
612 - }
613 -
614 - /**
615 - * @return String: Definition for this gadget from MediaWiki:gadgets-definition
616 - */
617 - public function getDefinition() {
618 - return $this->definition;
619 - }
620 -
621 - /**
622 - * @return Array: Array of pages with JS not prefixed with namespace
623 - */
624 - public function getScripts() {
625 - return $this->scripts;
626 - }
627 -
628 - /**
629 - * @return Array: Array of pages with CSS not prefixed with namespace
630 - */
631 - public function getStyles() {
632 - return $this->styles;
633 - }
634 -
635 - /**
636 - * @return Array: Array of all of this gadget's resources
637 - */
638 - public function getScriptsAndStyles() {
639 - return array_merge( $this->scripts, $this->styles );
640 - }
641 -
642 - /**
643 - * Returns module for ResourceLoader, see getModuleName() for its name.
644 - * If our gadget has no scripts or styles suitable for RL, false will be returned.
645 - * @return Mixed: GadgetResourceLoaderModule or false
646 - */
647 - public function getModule() {
648 - $pages = array();
649 - foreach( $this->styles as $style ) {
650 - $pages['MediaWiki:' . $style] = array( 'type' => 'style' );
651 - }
652 - if ( $this->supportsResourceLoader() ) {
653 - foreach ( $this->scripts as $script ) {
654 - $pages['MediaWiki:' . $script] = array( 'type' => 'script' );
655 - }
656 - }
657 - if ( !count( $pages ) ) {
658 - return null;
659 - }
660 - return new GadgetResourceLoaderModule( $pages, $this->dependencies, $this );
661 - }
662 -
663 - /**
664 - * Returns list of scripts that don't support ResourceLoader
665 - * @return Array
666 - */
667 - public function getLegacyScripts() {
668 - if ( $this->supportsResourceLoader() ) {
669 - return array();
670 - }
671 - return $this->scripts;
672 - }
673 -
674 - /**
675 - * Returns names of resources this gadget depends on
676 - * @return Array
677 - */
678 - public function getDependencies() {
679 - return $this->dependencies;
680 - }
681 -
682 - /**
683 - * Returns array of permissions required by this gadget
684 - * @return Array
685 - */
686 - public function getRequiredRights() {
687 - return $this->requiredRights;
688 - }
689 -
690 - /**
691 - * Loads and returns a list of all gadgets
692 - * @return Mixed: Array of gadgets or false
693 - */
694 - public static function loadList() {
695 - static $gadgets = null;
696 -
697 - if ( $gadgets !== null ) return $gadgets;
698 -
699 - wfProfileIn( __METHOD__ );
700 - $struct = self::loadStructuredList();
701 - if ( !$struct ) {
702 - $gadgets = $struct;
703 - wfProfileOut( __METHOD__ );
704 - return $gadgets;
705 - }
706 -
707 - $gadgets = array();
708 - foreach ( $struct as $section => $entries ) {
709 - $gadgets = array_merge( $gadgets, $entries );
710 - }
711 - wfProfileOut( __METHOD__ );
712 -
713 - return $gadgets;
714 - }
715 -
716 - /**
717 - * Checks whether gadget list from cache can be used.
718 - * @return Boolean
719 - */
720 - private static function isValidList( $gadgets ) {
721 - if ( !is_array( $gadgets ) ) return false;
722 - // Check if we have 1) array of gadgets 2) the gadgets are up to date
723 - // One check is enough
724 - foreach ( $gadgets as $section => $list ) {
725 - foreach ( $list as $g ) {
726 - if ( !( $g instanceof Gadget ) || $g->isOutdated() ) {
727 - return false;
728 - } else {
729 - return true;
730 - }
731 - }
732 - }
733 - return true; // empty array
734 - }
735 -
736 -
737 - /**
738 - * Loads list of gadgets and returns it as associative array of sections with gadgets
739 - * e.g. array( 'sectionnname1' => array( $gadget1, $gadget2),
740 - * 'sectionnname2' => array( $gadget3 ) );
741 - * @param $forceNewText String: New text of MediaWiki:gadgets-sdefinition. If specified, will
742 - * force a purge of cache and recreation of the gadget list.
743 - * @return Mixed: Array or false
744 - */
745 - public static function loadStructuredList( $forceNewText = null ) {
746 - global $wgMemc;
747 -
748 - static $gadgets = null;
749 -
750 - if ( $gadgets !== null && $forceNewText === null ) {
751 - return $gadgets;
752 - }
753 -
754 - wfProfileIn( __METHOD__ );
755 -
756 - $user = RequestContext::getMain()->getUser();
757 - if ( $user->isLoggedIn() ) {
758 - //Force loading user options
759 - //HACK: this may lead to loadStructuredList being recursively called.
760 - $user->getOptions();
761 -
762 - //Check again, loadStructuredList may have been called from UserLoadOptions hook handler;
763 - //in that case, we should just return current value instead of rebuilding the list again.
764 - //TODO: is there a better design?
765 - if ( $gadgets !== null && $forceNewText === null ) {
766 - wfProfileOut( __METHOD__ );
767 - return $gadgets;
768 - }
769 - }
770 -
771 - $key = wfMemcKey( 'gadgets-definition', self::GADGET_CLASS_VERSION );
772 -
773 - if ( $forceNewText === null ) {
774 - //cached?
775 - $gadgets = $wgMemc->get( $key );
776 - if ( self::isValidList( $gadgets ) ) {
777 - wfProfileOut( __METHOD__ );
778 - return $gadgets;
779 - }
780 -
781 - $g = wfMsgForContentNoTrans( "gadgets-definition" );
782 - if ( wfEmptyMsg( "gadgets-definition", $g ) ) {
783 - $gadgets = false;
784 - wfProfileOut( __METHOD__ );
785 - return $gadgets;
786 - }
787 - } else {
788 - $g = $forceNewText;
789 - }
790 -
791 - $g = preg_replace( '/<!--.*-->/s', '', $g );
792 - $g = preg_split( '/(\r\n|\r|\n)+/', $g );
793 -
794 - $gadgets = array();
795 - $section = '';
796 -
797 - foreach ( $g as $line ) {
798 - $m = array();
799 - if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
800 - $section = $m[1];
801 - }
802 - else {
803 - $gadget = self::newFromDefinition( $line );
804 - if ( $gadget ) {
805 - $gadgets[$section][$gadget->getName()] = $gadget;
806 - $gadget->category = $section;
807 - }
808 - }
809 - }
810 -
811 - //cache for a while. gets purged automatically when MediaWiki:Gadgets-definition is edited
812 - $wgMemc->set( $key, $gadgets, 60*60*24 );
813 - $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
814 - wfDebug( __METHOD__ . ": $source parsed, cache entry $key updated\n");
815 - wfProfileOut( __METHOD__ );
816 -
817 - return $gadgets;
818 - }
819 -
820 - //TODO: put the following static methods somewhere else
821 -
822 - //Checks if the given description of the preferences is valid
823 - public static function isGadgetPrefsDescriptionValid( &$prefsDescriptionJson ) {
824 - $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true );
825 -
826 - if ( $prefsDescription === null || !isset( $prefsDescription['fields'] ) ) {
827 - return false;
828 - }
829 -
830 - //Count of mandatory members for each type
831 - $mandatoryCount = array();
832 - foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) {
833 - $mandatoryCount[$type] = 0;
834 - foreach ( $typeSpec as $fieldName => $fieldSpec ) {
835 - if ( $fieldSpec['isMandatory'] === true ) {
836 - ++$mandatoryCount[$type];
837 - }
838 - }
839 - }
840 -
841 - //TODO: validation of members other than $prefs['fields']
842 -
843 - foreach ( $prefsDescription['fields'] as $option => $optionDefinition ) {
844 -
845 - //Check if 'type' is set and valid
846 - if ( !isset( $optionDefinition['type'] ) ) {
847 - return false;
848 - }
849 -
850 - $type = $optionDefinition['type'];
851 -
852 - if ( !isset( self::$prefsDescriptionSpecifications[$type] ) ) {
853 - return false;
854 - }
855 -
856 - //TODO: check $option name compliance
857 -
858 - //Check if all fields satisfy specification
859 - $typeSpec = self::$prefsDescriptionSpecifications[$type];
860 - $count = 0; //count of present mandatory members
861 - foreach ( $optionDefinition as $fieldName => $fieldValue ) {
862 -
863 - if ( $fieldName == 'type' ) {
864 - continue; //'type' must not be checked
865 - }
866 -
867 - if ( !isset( $typeSpec[$fieldName] ) ) {
868 - return false;
869 - }
870 -
871 - if ( $typeSpec[$fieldName]['isMandatory'] ) {
872 - ++$count;
873 - }
874 -
875 - if ( isset( $typeSpec[$fieldName]['checker'] ) ) {
876 - $checker = $typeSpec[$fieldName]['checker'];
877 - if ( !call_user_func( $checker, $fieldValue ) ) {
878 - return false;
879 - }
880 - }
881 - }
882 -
883 - if ( $count != $mandatoryCount[$type] ) {
884 - return false; //not all mandatory members are given
885 - }
886 -
887 - if ( isset( self::$typeCheckers[$type] ) ) {
888 - //Call type-specific checker for finer validation
889 - if ( !call_user_func( self::$typeCheckers[$type], $optionDefinition ) ) {
890 - return false;
891 - }
892 - }
893 - }
894 -
895 - return true;
896 - }
897 -
898 - /**
899 - * Gets description of preferences for this gadget.
900 - *
901 - * @return Mixed null if the gadget exists but doesn't have any preferences or if provided ones are not valid,
902 - * an array with the description of preferences otherwise.
903 - */
904 - public function getPrefsDescription() {
905 - $prefsDescriptionMsg = "Gadget-{$this->name}.preferences";
906 -
907 - //TODO: use cache
908 -
909 - $prefsDescriptionJson = wfMsgForContentNoTrans( $prefsDescriptionMsg );
910 - if ( wfEmptyMsg( $prefsDescriptionMsg, $prefsDescriptionJson ) ||
911 - !self::isGadgetPrefsDescriptionValid( $prefsDescriptionJson ) )
912 - {
913 - return null;
914 - }
915 -
916 - return FormatJson::decode( $prefsDescriptionJson, true );
917 - }
918 -
919 - //Check if a preference is valid, according to description
920 - //NOTE: we pass both $prefs and $prefName (instead of just $prefs[$prefName])
921 - // to allow checking for null.
922 - private static function checkSinglePref( $prefDescription, $prefs, $prefName ) {
923 -
924 - //isset( $prefs[$prefName] ) would return false for null values
925 - if ( !array_key_exists( $prefName, $prefs ) ) {
926 - return false;
927 - }
928 -
929 - $pref = $prefs[$prefName];
930 -
931 - switch ( $prefDescription['type'] ) {
932 - case 'boolean':
933 - return is_bool( $pref );
934 - case 'string':
935 - if ( !is_string( $pref ) ) {
936 - return false;
937 - }
938 -
939 - $len = strlen( $pref );
940 -
941 - //Checks the "required" option, if present
942 - $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
943 - if ( $required === true && $len == 0 ) {
944 - return false;
945 - } elseif ( $required === false && $len == 0 ) {
946 - return true; //overriding 'minlength'
947 - }
948 -
949 - //Checks the "minlength" option, if present
950 - $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0;
951 - if ( $len < $minlength ){
952 - return false;
953 - }
954 -
955 - //Checks the "maxlength" option, if present
956 - $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here?
957 - if ( $len > $maxlength ){
958 - return false;
959 - }
960 -
961 - return true;
962 - case 'number':
963 - if ( !is_float( $pref ) && !is_int( $pref ) && $pref !== null ) {
964 - return false;
965 - }
966 -
967 - $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
968 - if ( $required === false && $pref === null ) {
969 - return true;
970 - }
971 -
972 - if ( $pref === null ) {
973 - return false; //$required === true, so null is not acceptable
974 - }
975 -
976 - $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false;
977 -
978 - if ( $integer === true && intval( $pref ) != $pref ) {
979 - return false; //not integer
980 - }
981 -
982 - if ( isset( $prefDescription['min'] ) ) {
983 - $min = $prefDescription['min'];
984 - if ( $pref < $min ) {
985 - return false; //value below minimum
986 - }
987 - }
988 -
989 - if ( isset( $prefDescription['max'] ) ) {
990 - $max = $prefDescription['max'];
991 - if ( $pref > $max ) {
992 - return false; //value above maximum
993 - }
994 - }
995 -
996 - return true;
997 - case 'select':
998 - $values = array_values( $prefDescription['options'] );
999 - return in_array( $pref, $values, true );
1000 - default:
1001 - return false; //unexisting type
1002 - }
1003 - }
1004 -
1005 - /**
1006 - * Checks if $prefs is an array of preferences that passes validation
1007 - *
1008 - * @param $prefsDescription Array: the preferences description to use.
1009 - * @param &$prefs Array: reference of the array of preferences to check.
1010 - *
1011 - * @return boolean true if $prefs passes validation against $prefsDescription, false otherwise.
1012 - */
1013 - public static function checkPrefsAgainstDescription( $prefsDescription, $prefs ) {
1014 - foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
1015 - if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
1016 - return false;
1017 - }
1018 - }
1019 - return true;
1020 - }
1021 -
1022 - /**
1023 - * Fixes $prefs so that it matches the description given by $prefsDescription.
1024 - * All values of $prefs that fail validation are replaced with default values.
1025 - *
1026 - * @param $prefsDescription Array: the preferences description to use.
1027 - * @param &$prefs Array: reference of the array of preferences to match.
1028 - */
1029 - public static function matchPrefsWithDescription( $prefsDescription, &$prefs ) {
1030 - //Remove unexisting preferences from $prefs
1031 - foreach ( $prefs as $prefName => $value ) {
1032 - if ( !isset( $prefsDescription['fields'][$prefName] ) ) {
1033 - unset( $prefs[$prefName] );
1034 - }
1035 - }
1036 -
1037 - //Fix preferences that fail validation
1038 - foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
1039 - if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
1040 - $prefs[$prefName] = $prefDescription['default'];
1041 - }
1042 - }
1043 - }
1044 -
1045 - /**
1046 - * Returns current user's preferences for this gadget.
1047 - *
1048 - * @return Mixed the array of preferences if they have been set, null otherwise.
1049 - */
1050 - public function getPrefs() {
1051 - return $this->preferences;
1052 - }
1053 -
1054 - /**
1055 - * Sets current user's preferences for this gadget, after validating them.
1056 - *
1057 - * @param $prefs Array: the array of preferences.
1058 - * @param $savePrefs boolean: if true, preferences are also saved back to the Database.
1059 - *
1060 - * @return boolean: true if validation is passed, false otherwise.
1061 - */
1062 - public function setPrefs( $prefs, $savePrefs = false ) {
1063 -
1064 - if ( is_string( $prefs ) ) {
1065 - $prefs = FormatJson::decode( $prefs, true );
1066 - }
1067 -
1068 - if ( $prefs === null || !is_array( $prefs ) ) {
1069 - throw new MWException( __METHOD__ . ': $prefs must be an array or valid JSON' );
1070 - }
1071 -
1072 - $prefsDescription = $this->getPrefsDescription();
1073 -
1074 - if ( $prefsDescription === null ) {
1075 - return false; //nothing to save
1076 - }
1077 -
1078 - if ( !self::checkPrefsAgainstDescription( $prefsDescription, $prefs ) ) {
1079 - return false; //validation failed
1080 - }
1081 -
1082 - $this->preferences = $prefs;
1083 -
1084 - if ( $savePrefs ) {
1085 - $user = RequestContext::getMain()->getUser();
1086 - $user->saveSettings();
1087 - }
1088 - return true;
1089 - }
1090 -}
1091 -
1092 -/**
1093 - * Class representing a list of resources for one gadget
1094 - */
1095 -class GadgetResourceLoaderModule extends ResourceLoaderWikiModule {
1096 - private $pages, $dependencies, $gadget;
1097 -
1098 - /**
1099 - * Creates an instance of this class
1100 - * @param $pages Array: Associative array of pages in ResourceLoaderWikiModule-compatible
1101 - * format, for example:
1102 - * array(
1103 - * 'MediaWiki:Gadget-foo.js' => array( 'type' => 'script' ),
1104 - * 'MediaWiki:Gadget-foo.css' => array( 'type' => 'style' ),
1105 - * )
1106 - * @param $dependencies Array: Names of resources this module depends on
1107 - */
1108 - public function __construct( $pages, $dependencies, $gadget ) {
1109 - $this->pages = $pages;
1110 - $this->dependencies = $dependencies;
1111 - $this->gadget = $gadget;
1112 - }
1113 -
1114 - /**
1115 - * Overrides the abstract function from ResourceLoaderWikiModule class
1116 - * @return Array: $pages passed to __construct()
1117 - */
1118 - protected function getPages( ResourceLoaderContext $context ) {
1119 - return $this->pages;
1120 - }
1121 -
1122 - /**
1123 - * Overrides ResourceLoaderModule::getDependencies()
1124 - * @return Array: Names of resources this module depends on
1125 - */
1126 - public function getDependencies() {
1127 - return $this->dependencies;
1128 - }
1129 -
1130 - public function getScript( ResourceLoaderContext $context ) {
1131 - $prefs = $this->gadget->getPrefs();
1132 -
1133 - //Enclose gadget's code in a closure, with "this" bound to the
1134 - //configuration object (or to "window" for non-configurable gadgets)
1135 - $header = '(function(){';
1136 -
1137 - //TODO: it may be nice add other metadata for the gadget
1138 - $boundObject = array( 'config' => $prefs );
1139 -
1140 - if ( $prefs !== NULL ) {
1141 - //Bind configuration object to "this".
1142 - $footer = '}).' . Xml::encodeJsCall( 'apply',
1143 - array( $boundObject, array() )
1144 - ) . ';';
1145 - } else {
1146 - //Bind window to "this"
1147 - $footer = '}).apply( window, [] );';
1148 - }
1149 -
1150 - return $header . parent::getScript( $context ) . $footer;
1151 - }
1152 -
1153 -
1154 - //TODO: should depend on last modification time of gadget's configuration page, also
1155 - public function getModifiedTime( ResourceLoaderContext $context ) {
1156 - $touched = RequestContext::getMain()->getUser()->getTouched();
1157 -
1158 - return max( parent::getModifiedTime( $context ), wfTimestamp( TS_UNIX, $touched ) );
1159 - }
1160 -}
1161 -
1162 -//Implements ext.gadgets. Required by ext.gadgets.preferences
1163 -class GadgetsGlobalModule extends ResourceLoaderModule {
1164 - //TODO: should override getModifiedTime()
1165 -
1166 - public function getScript( ResourceLoaderContext $context ) {
1167 - $configurableGadgets = array();
1168 - $gadgetsList = Gadget::loadStructuredList();
1169 -
1170 - foreach ( $gadgetsList as $section => $gadgets ) {
1171 - foreach ( $gadgets as $gadgetName => $gadget ) {
1172 - $prefs = $gadget->getPrefsDescription();
1173 - if ( $prefs !== null ) {
1174 - $configurableGadgets[] = $gadget->getName();
1175 - }
1176 - }
1177 - }
1178 -
1179 - $script = "mw.gadgets = {}\n";
1180 - $script .= "mw.gadgets.configurableGadgets = " . Xml::encodeJsVar( $configurableGadgets ) . ";\n";
1181 - return $script;
1182 - }
1183 -}
Index: branches/salvatoreingala/Gadgets/ApiQueryGadgets.php
@@ -1,208 +0,0 @@
2 -<?php
3 -/**
4 - * Created on 15 April 2011
5 - * API for Gadgets extension
6 - *
7 - * This program is free software; you can redistribute it and/or modify
8 - * it under the terms of the GNU General Public License as published by
9 - * the Free Software Foundation; either version 2 of the License, or
10 - * (at your option) any later version.
11 - *
12 - * This program is distributed in the hope that it will be useful,
13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 - * GNU General Public License for more details.
16 - *
17 - * You should have received a copy of the GNU General Public License along
18 - * with this program; if not, write to the Free Software Foundation, Inc.,
19 - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 - * http://www.gnu.org/copyleft/gpl.html
21 - */
22 -
23 -class ApiQueryGadgets extends ApiQueryBase {
24 - private $props,
25 - $category,
26 - $neededNames,
27 - $listAllowed,
28 - $listEnabled;
29 -
30 - public function __construct( $query, $moduleName ) {
31 - parent::__construct( $query, $moduleName, 'ga' );
32 - }
33 -
34 - public function execute() {
35 - $params = $this->extractRequestParams();
36 - $this->props = array_flip( $params['prop'] );
37 - $this->categories = isset( $params['categories'] )
38 - ? array_flip( $params['categories'] )
39 - : false;
40 - $this->neededNames = isset( $params['names'] )
41 - ? array_flip( $params['names'] )
42 - : false;
43 - $this->listAllowed = isset( $params['allowed'] ) && $params['allowed'];
44 - $this->listEnabled = isset( $params['enabled'] ) && $params['enabled'];
45 -
46 - $this->getMain()->setCacheMode( $this->listAllowed || $this->listEnabled
47 - ? 'anon-public-user-private' : 'public' );
48 -
49 - $this->applyList( $this->getList() );
50 - }
51 -
52 - private function getList() {
53 - $gadgets = Gadget::loadStructuredList();
54 -
55 - $result = array();
56 - foreach ( $gadgets as $category => $list ) {
57 - if ( $this->categories && !isset( $this->categories[$category] ) ) {
58 - continue;
59 - }
60 - foreach ( $list as $g ) {
61 - if ( $this->isNeeded( $g ) ) {
62 - $result[] = $g;
63 - }
64 - }
65 - }
66 - return $result;
67 - }
68 -
69 - private function applyList( $gadgets ) {
70 - $data = array();
71 - $result = $this->getResult();
72 -
73 - foreach ( $gadgets as $g ) {
74 - $row = array();
75 - if ( isset( $this->props['name'] ) ) {
76 - $row['name'] = $g->getName();
77 - }
78 - if ( isset( $this->props['desc'] ) ) {
79 - $row['desc'] = $g->getDescription();
80 - }
81 - if ( isset( $this->props['desc-raw'] ) ) {
82 - $row['desc-raw'] = $g->getRawDescription();
83 - }
84 - if ( isset( $this->props['category'] ) ) {
85 - $row['category'] = $g->getCategory();
86 - }
87 - if ( isset( $this->props['resourceloader'] ) && $g->supportsResourceLoader() ) {
88 - $row['resourceloader'] = '';
89 - }
90 - if ( isset( $this->props['scripts'] ) ) {
91 - $row['scripts'] = $g->getScripts();
92 - $result->setIndexedTagName( $row['scripts'], 'script' );
93 - }
94 - if ( isset( $this->props['styles'] ) ) {
95 - $row['styles'] = $g->getStyles();
96 - $result->setIndexedTagName( $row['styles'], 'style' );
97 - }
98 - if ( isset( $this->props['dependencies'] ) ) {
99 - $row['dependencies'] = $g->getDependencies();
100 - $result->setIndexedTagName( $row['dependencies'], 'module' );
101 - }
102 - if ( isset( $this->props['rights'] ) ) {
103 - $row['rights'] = $g->getRequiredRights();
104 - $result->setIndexedTagName( $row['rights'], 'right' );
105 - }
106 - if ( isset( $this->props['default'] ) && $g->isOnByDefault() ) {
107 - $row['default'] = '';
108 - }
109 - if ( isset( $this->props['definition'] ) ) {
110 - $row['definition'] = $g->getDefinition();
111 - }
112 - $data[] = $row;
113 - }
114 - $result->setIndexedTagName( $data, 'gadget' );
115 - $result->addValue( 'query', $this->getModuleName(), $data );
116 - }
117 -
118 - /**
119 - *
120 - */
121 - private function isNeeded( Gadget $gadget ) {
122 - global $wgUser;
123 -
124 - return ( $this->neededNames === false || isset( $this->neededNames[$gadget->getName()] ) )
125 - && ( !$this->listAllowed || $gadget->isAllowed( $wgUser ) )
126 - && ( !$this->listEnabled || $gadget->isEnabled( $wgUser ) );
127 - }
128 -
129 - public function getAllowedParams() {
130 - return array(
131 - 'prop' => array(
132 - ApiBase::PARAM_DFLT => 'name',
133 - ApiBase::PARAM_ISMULTI => true,
134 - ApiBase::PARAM_TYPE => array(
135 - 'name',
136 - 'desc',
137 - 'desc-raw',
138 - 'category',
139 - 'resourceloader',
140 - 'scripts',
141 - 'styles',
142 - 'dependencies',
143 - 'rights',
144 - 'default',
145 - 'definition',
146 - ),
147 - ),
148 - 'categories' => array(
149 - ApiBase::PARAM_ISMULTI => true,
150 - ApiBase::PARAM_TYPE => 'string',
151 - ),
152 - 'names' => array(
153 - ApiBase::PARAM_TYPE => 'string',
154 - ApiBase::PARAM_ISMULTI => true,
155 - ),
156 - 'allowed' => false,
157 - 'enabled' => false,
158 - );
159 - }
160 -
161 - public function getDescription() {
162 - return 'Returns a list of gadgets used on this wiki';
163 - }
164 -
165 - public function getParamDescription() {
166 - return array(
167 - 'prop' => array(
168 - 'What gadget information to get:',
169 - ' name - Internal gadget name',
170 - ' desc - Gadget description transformed into HTML (can be slow, use only if really needed)',
171 - ' desc-raw - Gadget description in raw wikitext',
172 - ' category - Internal name of a category gadget belongs to (empty if top-level gadget)',
173 - ' resourceloader - Whether gadget supports ResourceLoader',
174 - " scripts - List of gadget's scripts",
175 - " styles - List of gadget's styles",
176 - ' dependencies - List of ResourceLoader modules gadget depends on',
177 - ' rights - List of rights required to use gadget, if any',
178 - ' default - Whether gadget is enabled by default',
179 - ' definition - Line from MediaWiki:Gadgets-definition used to define the gadget',
180 - ),
181 - 'categories' => 'Gadgets from what categories to retrieve',
182 - 'names' => 'Name(s) of gadgets to retrieve',
183 - 'allowed' => 'List only gadgets allowed to current user',
184 - 'enabled' => 'List only gadgets enabled by current user',
185 - );
186 - }
187 -
188 - protected function getExamples() {
189 - $params = $this->getAllowedParams();
190 - $allProps = implode( '|', $params['prop'][ApiBase::PARAM_TYPE] );
191 - return array(
192 - 'Get a list of gadgets along with their descriptions:',
193 - ' api.php?action=query&list=gadgets&gaprop=name|desc',
194 - 'Get a list of gadgets with all possble properties:',
195 - " api.php?action=query&list=gadgets&gaprop=$allProps",
196 - 'Get a list of gadgets belonging to caregory "foo":',
197 - ' api.php?action=query&list=gadgets&gacategories=foo',
198 - 'Get information about gadgets named "foo" and "bar":',
199 - ' api.php?action=query&list=gadgets&ganames=foo|bar&gaprop=name|desc|category',
200 - 'Get a list of gadgets enabled by current user:',
201 - ' api.php?action=query&list=gadgets&gaenabled',
202 - );
203 - }
204 -
205 - public function getVersion() {
206 - return __CLASS__ . ': $Id$';
207 - }
208 -
209 -}
Index: branches/salvatoreingala/Gadgets/SpecialGadgets.php
@@ -1,167 +0,0 @@
2 -<?php
3 -/**
4 - * Special:Gadgets, provides a preview of MediaWiki:Gadgets.
5 - *
6 - * @file
7 - * @ingroup SpecialPage
8 - * @author Daniel Kinzler, brightbyte.de
9 - * @copyright © 2007 Daniel Kinzler
10 - * @license GNU General Public License 2.0 or later
11 - */
12 -
13 -if( !defined( 'MEDIAWIKI' ) ) {
14 - echo( "not a valid entry point.\n" );
15 - die( 1 );
16 -}
17 -
18 -/**
19 - *
20 - */
21 -class SpecialGadgets extends SpecialPage {
22 -
23 - /**
24 - * Constructor
25 - */
26 - function __construct() {
27 - parent::__construct( 'Gadgets', '', true );
28 - }
29 -
30 - /**
31 - * Main execution function
32 - * @param $par Parameters passed to the page
33 - */
34 - function execute( $par ) {
35 - $parts = explode( '/', $par );
36 - if ( count( $parts ) == 2 && $parts[0] == 'export' ) {
37 - $this->showExportForm( $parts[1] );
38 - } else {
39 - $this->showMainForm();
40 - }
41 - }
42 -
43 - /**
44 - * Displays form showing the list of installed gadgets
45 - */
46 - public function showMainForm() {
47 - global $wgOut, $wgUser, $wgLang, $wgContLang;
48 -
49 - $skin = $wgUser->getSkin();
50 -
51 - $this->setHeaders();
52 - $wgOut->setPagetitle( wfMsg( "gadgets-title" ) );
53 - $wgOut->addWikiMsg( 'gadgets-pagetext' );
54 -
55 - $gadgets = Gadget::loadStructuredList();
56 - if ( !$gadgets ) return;
57 -
58 - $lang = "";
59 - if ( $wgLang->getCode() != $wgContLang->getCode() ) {
60 - $lang = "/" . $wgLang->getCode();
61 - }
62 -
63 - $listOpen = false;
64 -
65 - $msgOpt = array( 'parseinline', 'parsemag' );
66 - $editInterfaceAllowed = $wgUser->isAllowed( 'editinterface' );
67 -
68 - foreach ( $gadgets as $section => $entries ) {
69 - if ( $section !== false && $section !== '' ) {
70 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-section-$section$lang" );
71 - if ( $editInterfaceAllowed ) {
72 - $lnkTarget = $t
73 - ? $skin->link( $t, wfMsgHTML( 'edit' ), array(), array( 'action' => 'edit' ) )
74 - : htmlspecialchars( $section );
75 - $lnk = "&#160; &#160; [$lnkTarget]";
76 - } else {
77 - $lnk = '';
78 - }
79 - $ttext = wfMsgExt( "gadget-section-$section", $msgOpt );
80 -
81 - if( $listOpen ) {
82 - $wgOut->addHTML( Xml::closeElement( 'ul' ) . "\n" );
83 - $listOpen = false;
84 - }
85 - $wgOut->addHTML( Html::rawElement( 'h2', array(), $ttext . $lnk ) . "\n" );
86 - }
87 -
88 - foreach ( $entries as $gadget ) {
89 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-{$gadget->getName()}$lang" );
90 - if ( !$t ) continue;
91 -
92 - $links = array();
93 - if ( $editInterfaceAllowed ) {
94 - $links[] = $skin->link( $t, wfMsgHTML( 'edit' ), array(), array( 'action' => 'edit' ) );
95 - }
96 - $links[] = $skin->link( $this->getTitle( "export/{$gadget->getName()}" ), wfMsgHtml( 'gadgets-export' ) );
97 -
98 - $ttext = wfMsgExt( "gadget-{$gadget->getName()}", $msgOpt );
99 -
100 - if( !$listOpen ) {
101 - $listOpen = true;
102 - $wgOut->addHTML( Xml::openElement( 'ul' ) );
103 - }
104 - $lnk = '&#160;&#160;' . wfMsg( 'parentheses', $wgLang->pipeList( $links ) );
105 - $wgOut->addHTML( Xml::openElement( 'li' ) .
106 - $ttext . $lnk . "<br />" .
107 - wfMsgHTML( 'gadgets-uses' ) . wfMsg( 'colon-separator' )
108 - );
109 -
110 - $lnk = array();
111 - foreach ( $gadget->getScriptsAndStyles() as $codePage ) {
112 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, $codePage );
113 - if ( !$t ) continue;
114 -
115 - $lnk[] = $skin->link( $t, htmlspecialchars( $t->getText() ) );
116 - }
117 - $wgOut->addHTML( $wgLang->commaList( $lnk ) );
118 - $rights = $gadget->getRequiredRights();
119 - if ( count( $rights ) ) {
120 - $wgOut->addHTML( '<br />' .
121 - wfMessage( 'gadgets-required-rights', $wgLang->commaList( $rights ), count( $rights ) )->parse()
122 - );
123 - }
124 - if ( $gadget->isOnByDefault() ) {
125 - $wgOut->addHTML( '<br />' . wfMessage( 'gadgets-default' )->parse() );
126 - }
127 -
128 - $wgOut->addHTML( Xml::closeElement( 'li' ) . "\n" );
129 - }
130 - }
131 -
132 - if( $listOpen ) {
133 - $wgOut->addHTML( Xml::closeElement( 'ul' ) . "\n" );
134 - }
135 - }
136 -
137 - /**
138 - * Exports a gadget with its dependencies in a serialized form
139 - * @param $gadget String Name of gadget to export
140 - */
141 - public function showExportForm( $gadget ) {
142 - global $wgOut, $wgScript;
143 -
144 - $gadgets = Gadget::loadList();
145 - if ( !isset( $gadgets[$gadget] ) ) {
146 - $wgOut->showErrorPage( 'error', 'gadgets-not-found', array( $gadget ) );
147 - return;
148 - }
149 -
150 - $g = $gadgets[$gadget];
151 - $this->setHeaders();
152 - $wgOut->setPagetitle( wfMsg( "gadgets-export-title" ) );
153 - $wgOut->addWikiMsg( 'gadgets-export-text', $gadget, $g->getDefinition() );
154 -
155 - $exportList = "MediaWiki:gadget-$gadget\n";
156 - foreach ( $g->getScriptsAndStyles() as $page ) {
157 - $exportList .= "MediaWiki:$page\n";
158 - }
159 -
160 - $wgOut->addHTML( Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) )
161 - . Html::hidden( 'title', SpecialPage::getTitleFor( 'Export' )->getPrefixedDBKey() )
162 - . Html::hidden( 'pages', $exportList )
163 - . Html::hidden( 'wpDownload', '1' )
164 - . Xml::submitButton( wfMsg( 'gadgets-export-download' ) )
165 - . Html::closeElement( 'form' )
166 - );
167 - }
168 -}
Index: branches/salvatoreingala/Gadgets/ApiQueryGadgetCategories.php
@@ -1,122 +0,0 @@
2 -<?php
3 -/**
4 - * Created on 16 April 2011
5 - * API for Gadgets extension
6 - *
7 - * This program is free software; you can redistribute it and/or modify
8 - * it under the terms of the GNU General Public License as published by
9 - * the Free Software Foundation; either version 2 of the License, or
10 - * (at your option) any later version.
11 - *
12 - * This program is distributed in the hope that it will be useful,
13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 - * GNU General Public License for more details.
16 - *
17 - * You should have received a copy of the GNU General Public License along
18 - * with this program; if not, write to the Free Software Foundation, Inc.,
19 - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 - * http://www.gnu.org/copyleft/gpl.html
21 - */
22 -
23 -class ApiQueryGadgetCategories extends ApiQueryBase {
24 - private $props,
25 - $neededNames;
26 -
27 - public function __construct( $query, $moduleName ) {
28 - parent::__construct( $query, $moduleName, 'gc' );
29 - }
30 -
31 - public function execute() {
32 - $params = $this->extractRequestParams();
33 - $this->props = array_flip( $params['prop'] );
34 - $this->neededNames = isset( $params['names'] )
35 - ? array_flip( $params['names'] )
36 - : false;
37 -
38 - $this->getMain()->setCacheMode( 'public' );
39 -
40 - $this->getList();
41 - }
42 -
43 - private function getList() {
44 - $data = array();
45 - $result = $this->getResult();
46 - $gadgets = Gadget::loadStructuredList();
47 -
48 - foreach ( $gadgets as $category => $list ) {
49 - if ( !$this->neededNames || isset( $this->neededNames[$category] ) ) {
50 - $row = array();
51 - if ( isset( $this->props['name'] ) ) {
52 - $row['name'] = $category;
53 - }
54 - if ( $category !== "" ) {
55 - if ( isset( $this->props['desc'] ) ) {
56 - $row['desc'] = wfMessage( "gadget-section-$category" )->parse();
57 - }
58 - if ( isset( $this->props['desc-raw'] ) ) {
59 - $row['desc-raw'] = wfMessage( "gadget-section-$category" )->plain();
60 - }
61 - }
62 - if ( isset( $this->props['members'] ) ) {
63 - $row['members'] = count( $list );
64 - }
65 - $data[] = $row;
66 - }
67 - }
68 - $result->setIndexedTagName( $data, 'category' );
69 - $result->addValue( 'query', $this->getModuleName(), $data );
70 - }
71 -
72 - public function getAllowedParams() {
73 - return array(
74 - 'prop' => array(
75 - ApiBase::PARAM_DFLT => 'name',
76 - ApiBase::PARAM_ISMULTI => true,
77 - ApiBase::PARAM_TYPE => array(
78 - 'name',
79 - 'desc',
80 - 'desc-raw',
81 - 'members',
82 - ),
83 - ),
84 - 'names' => array(
85 - ApiBase::PARAM_TYPE => 'string',
86 - ApiBase::PARAM_ISMULTI => true,
87 - ),
88 - );
89 - }
90 -
91 - public function getDescription() {
92 - return 'Returns a list of gadget categories';
93 - }
94 -
95 - public function getParamDescription() {
96 - return array(
97 - 'prop' => array(
98 - 'What gadget category information to get:',
99 - ' name - Internal category name',
100 - ' desc - Category description transformed into HTML (can be slow, use only if really needed)',
101 - ' desc-raw - Category description in raw wikitext',
102 - ' members - Number of gadgets in category',
103 - ),
104 - 'names' => 'Name(s) of gadgets to retrieve',
105 - );
106 - }
107 -
108 - protected function getExamples() {
109 - $params = $this->getAllowedParams();
110 - $allProps = implode( '|', $params['prop'][ApiBase::PARAM_TYPE] );
111 - return array(
112 - 'Get a list of existing gadget categories:',
113 - ' api.php?action=query&list=gadgetcategories',
114 - 'Get all information about categories named "foo" and "bar":',
115 - " api.php?action=query&list=gadgetcategories&gcnames=foo|bar&gcprop=$allProps",
116 - );
117 - }
118 -
119 - public function getVersion() {
120 - return __CLASS__ . ': $Id$';
121 - }
122 -
123 -}
Index: branches/salvatoreingala/Gadgets/ApiSetGadgetPrefs.php
@@ -1,122 +0,0 @@
2 -<?php
3 -
4 -/**
5 - *
6 - * API for setting Gadget's preferences
7 - *
8 - * This program is free software; you can redistribute it and/or modify
9 - * it under the terms of the GNU General Public License as published by
10 - * the Free Software Foundation; either version 2 of the License, or
11 - * (at your option) any later version.
12 - *
13 - * This program is distributed in the hope that it will be useful,
14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 - * GNU General Public License for more details.
17 - *
18 - * You should have received a copy of the GNU General Public License along
19 - * with this program; if not, write to the Free Software Foundation, Inc.,
20 - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 - * http://www.gnu.org/copyleft/gpl.html
22 - */
23 -
24 -class ApiSetGadgetPrefs extends ApiBase {
25 -
26 - public function execute() {
27 - $user = RequestContext::getMain()->getUser();
28 -
29 - $params = $this->extractRequestParams();
30 - //Check permissions
31 - if ( !$user->isLoggedIn() ) {
32 - $this->dieUsage( 'You must be logged-in to set gadget\'s preferences', 'notloggedin' );
33 - }
34 -
35 - //Check token
36 - if ( !$user->matchEditToken( $params['token'] ) ) {
37 - $this->dieUsageMsg( 'sessionfailure' );
38 - }
39 -
40 - $gadgetName = $params['gadget'];
41 - $gadgets = Gadget::loadList();
42 - $gadget = $gadgets && isset( $gadgets[$gadgetName] ) ? $gadgets[$gadgetName] : null;
43 -
44 - if ( $gadget === null ) {
45 - $this->dieUsage( 'Gadget not found', 'notfound' );
46 - }
47 -
48 - $prefsJson = $params['prefs'];
49 - $prefs = FormatJson::decode( $prefsJson, true );
50 -
51 - if ( !is_array( $prefs ) ) {
52 - $this->dieUsage( 'The \'pref\' parameter must be valid JSON', 'notjson' );
53 - }
54 -
55 - $result = $gadget->setPrefs( $prefs, true );
56 -
57 - if ( $result === true ) {
58 - $this->getResult()->addValue(
59 - null, $this->getModuleName(), array( 'result' => 'Success' ) );
60 - } else {
61 - $this->dieUsage( 'Invalid preferences', 'invalidprefs' );
62 - }
63 - }
64 -
65 - public function mustBePosted() {
66 - return true;
67 - }
68 -
69 - public function isWriteMode() {
70 - return true;
71 - }
72 -
73 - public function getAllowedParams() {
74 - return array(
75 - 'gadget' => array(
76 - ApiBase::PARAM_TYPE => 'string',
77 - ApiBase::PARAM_REQUIRED => true
78 - ),
79 - 'prefs' => array(
80 - ApiBase::PARAM_TYPE => 'string',
81 - ApiBase::PARAM_REQUIRED => true
82 - ),
83 - 'token' => array(
84 - ApiBase::PARAM_TYPE => 'string',
85 - ApiBase::PARAM_REQUIRED => true
86 - ),
87 - );
88 - }
89 -
90 - public function getParamDescription() {
91 - return array(
92 - 'gadget' => 'The name of the gadget',
93 - 'prefs' => 'The new preferences in JSON format',
94 - 'token' => 'An edit token'
95 - );
96 - }
97 -
98 - public function getDescription() {
99 - return 'Allows user code to set preferences for gadgets';
100 - }
101 -
102 - public function getPossibleErrors() {
103 - return array_merge( parent::getPossibleErrors(), array(
104 - array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to get gadget\'s preferences' ),
105 - array( 'sessionfailure' ),
106 - array( 'code' => 'notfound', 'info' => 'Gadget not found' ),
107 - array( 'code' => 'notjson', 'info' => 'The \'pref\' parameter must be valid JSON' ),
108 - array( 'code' => 'invalidprefs', 'info' => 'Invalid preferences' ),
109 - ) );
110 - }
111 -
112 - public function needsToken() {
113 - return true;
114 - }
115 -
116 - public function getSalt() {
117 - return '';
118 - }
119 -
120 - public function getVersion() {
121 - return __CLASS__ . ': $Id$';
122 - }
123 -}
Index: branches/salvatoreingala/Gadgets/ApiGetGadgetPrefs.php
@@ -1,93 +0,0 @@
2 -<?php
3 -
4 -/**
5 - *
6 - * API for setting Gadget's preferences
7 - *
8 - * This program is free software; you can redistribute it and/or modify
9 - * it under the terms of the GNU General Public License as published by
10 - * the Free Software Foundation; either version 2 of the License, or
11 - * (at your option) any later version.
12 - *
13 - * This program is distributed in the hope that it will be useful,
14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 - * GNU General Public License for more details.
17 - *
18 - * You should have received a copy of the GNU General Public License along
19 - * with this program; if not, write to the Free Software Foundation, Inc.,
20 - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 - * http://www.gnu.org/copyleft/gpl.html
22 - */
23 -
24 -class ApiGetGadgetPrefs extends ApiBase {
25 -
26 - public function execute() {
27 - $user = RequestContext::getMain()->getUser();
28 -
29 - $params = $this->extractRequestParams();
30 - //Check permissions
31 - if ( !$user->isLoggedIn() ) {
32 - $this->dieUsage( 'You must be logged-in to get gadget\'s preferences', 'notloggedin' );
33 - }
34 -
35 - $gadgetName = $params['gadget'];
36 -
37 - $gadgets = Gadget::loadList();
38 - $gadget = $gadgets && isset( $gadgets[$gadgetName] ) ? $gadgets[$gadgetName] : null;
39 -
40 - if ( $gadget === null ) {
41 - $this->dieUsage( 'Gadget not found', 'notfound' );
42 - }
43 -
44 - $prefsDescription = $gadget->getPrefsDescription();
45 -
46 - if ( $prefsDescription === null ) {
47 - $this->dieUsage( 'Gadget ' . $gadget->getName() . ' does not have any preference', 'noprefs' );
48 - }
49 -
50 - $userPrefs = $gadget->getPrefs();
51 -
52 - if ( $userPrefs === null ) {
53 - $this->dieUsage( 'An unexpected condition happened, please report this to the developers', 'unexpectederror' );
54 - }
55 -
56 - //Add user preferences to preference description
57 - foreach ( $userPrefs as $pref => $value ) {
58 - $prefsDescription['fields'][$pref]['value'] = $value;
59 - }
60 -
61 - $this->getResult()->addValue( null, $this->getModuleName(), $prefsDescription );
62 - }
63 -
64 - public function getAllowedParams() {
65 - return array(
66 - 'gadget' => array(
67 - ApiBase::PARAM_TYPE => 'string',
68 - ApiBase::PARAM_REQUIRED => true
69 - )
70 - );
71 - }
72 -
73 - public function getParamDescription() {
74 - return array(
75 - 'gadget' => 'The name of the gadget'
76 - );
77 - }
78 -
79 - public function getDescription() {
80 - return 'Allows user code to get preferences for gadgets, along with preference descriptions and values for currently logged in user';
81 - }
82 -
83 - public function getPossibleErrors() {
84 - return array_merge( parent::getPossibleErrors(), array(
85 - array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to get gadget\'s preferences' ),
86 - array( 'code' => 'notfound', 'info' => 'Gadget not found' ),
87 - array( 'code' => 'noprefs', 'info' => 'Gadget gadgetname does not have any preferences' ),
88 - ) );
89 - }
90 -
91 - public function getVersion() {
92 - return __CLASS__ . ': $Id$';
93 - }
94 -}
Index: branches/salvatoreingala/Gadgets/Gadgets.php
@@ -41,15 +41,15 @@
4242 $wgExtensionMessagesFiles['Gadgets'] = $dir . 'Gadgets.i18n.php';
4343 $wgExtensionAliasesFiles['Gadgets'] = $dir . 'Gadgets.alias.php';
4444
45 -$wgAutoloadClasses['ApiQueryGadgetCategories'] = $dir . 'ApiQueryGadgetCategories.php';
46 -$wgAutoloadClasses['ApiQueryGadgets'] = $dir . 'ApiQueryGadgets.php';
47 -$wgAutoloadClasses['Gadget'] = $dir . 'Gadgets_body.php';
48 -$wgAutoloadClasses['GadgetHooks'] = $dir . 'Gadgets_body.php';
49 -$wgAutoloadClasses['GadgetResourceLoaderModule'] = $dir . 'Gadgets_body.php';
50 -$wgAutoloadClasses['SpecialGadgets'] = $dir . 'SpecialGadgets.php';
51 -$wgAutoloadClasses['GadgetsGlobalModule'] = $dir . 'Gadgets_body.php';
52 -$wgAutoloadClasses['ApiSetGadgetPrefs'] = $dir . 'ApiSetGadgetPrefs.php';
53 -$wgAutoloadClasses['ApiGetGadgetPrefs'] = $dir . 'ApiGetGadgetPrefs.php';
 45+$wgAutoloadClasses['ApiQueryGadgetCategories'] = $dir . 'api/ApiQueryGadgetCategories.php';
 46+$wgAutoloadClasses['ApiQueryGadgets'] = $dir . 'api/ApiQueryGadgets.php';
 47+$wgAutoloadClasses['ApiGetGadgetPrefs'] = $dir . 'api/ApiGetGadgetPrefs.php';
 48+$wgAutoloadClasses['ApiSetGadgetPrefs'] = $dir . 'api/ApiSetGadgetPrefs.php';
 49+$wgAutoloadClasses['Gadget'] = $dir . 'backend/Gadget.php';
 50+$wgAutoloadClasses['GadgetHooks'] = $dir . 'backend/GadgetHooks.php';
 51+$wgAutoloadClasses['GadgetResourceLoaderModule'] = $dir . 'backend/GadgetResourceLoaderModule.php';
 52+$wgAutoloadClasses['GadgetsMainModule'] = $dir . 'ui/GadgetsMainModule.php';
 53+$wgAutoloadClasses['SpecialGadgets'] = $dir . 'ui/SpecialGadgets.php';
5454
5555 $wgSpecialPages['Gadgets'] = 'SpecialGadgets';
5656 $wgSpecialPageGroups['Gadgets'] = 'wiki';
@@ -61,14 +61,14 @@
6262 $wgAjaxExportList[] = 'GadgetsAjax::setPreferences';
6363
6464 $wgResourceModules['ext.gadgets'] = array(
65 - 'class' => 'GadgetsGlobalModule'
 65+ 'class' => 'GadgetsMainModule'
6666 );
6767
6868 $wgResourceModules['jquery.validate'] = array(
6969 'scripts' => array( 'jquery.validate.js' ),
7070 'dependencies' => array( 'jquery' ),
71 - 'localBasePath' => $dir . 'modules/',
72 - 'remoteExtPath' => 'Gadgets/modules'
 71+ 'localBasePath' => $dir . 'ui/resources/',
 72+ 'remoteExtPath' => 'Gadgets/ui/resources'
7373 );
7474
7575 $wgResourceModules['jquery.formBuilder'] = array(
@@ -78,8 +78,8 @@
7979 'gadgets-formbuilder-required', 'gadgets-formbuilder-minlength', 'gadgets-formbuilder-maxlength',
8080 'gadgets-formbuilder-min', 'gadgets-formbuilder-max', 'gadgets-formbuilder-integer'
8181 ),
82 - 'localBasePath' => $dir . 'modules/',
83 - 'remoteExtPath' => 'Gadgets/modules'
 82+ 'localBasePath' => $dir . 'ui/resources/',
 83+ 'remoteExtPath' => 'Gadgets/ui/resources'
8484 );
8585
8686 $wgResourceModules['ext.gadgets.preferences'] = array(
@@ -93,6 +93,6 @@
9494 'gadgets-configure', 'gadgets-configuration-of', 'gadgets-prefs-save', 'gadgets-prefs-cancel',
9595 'gadgets-unexpected-error', 'gadgets-save-success', 'gadgets-save-failed'
9696 ),
97 - 'localBasePath' => $dir . 'modules/',
98 - 'remoteExtPath' => 'Gadgets/modules'
 97+ 'localBasePath' => $dir . 'ui/resources/',
 98+ 'remoteExtPath' => 'Gadgets/ui/resources'
9999 );
Index: branches/salvatoreingala/Gadgets/backend/Gadget.php
@@ -0,0 +1,828 @@
 2+<?php
 3+
 4+/**
 5+ * Gadgets extension - lets users select custom javascript gadgets
 6+ *
 7+ *
 8+ * For more info see http://mediawiki.org/wiki/Extension:Gadgets
 9+ *
 10+ * @file
 11+ * @ingroup Extensions
 12+ * @author Daniel Kinzler, brightbyte.de
 13+ * @copyright © 2007 Daniel Kinzler
 14+ * @license GNU General Public Licence 2.0 or later
 15+ */
 16+
 17+/**
 18+ * Wrapper for one gadget.
 19+ */
 20+class Gadget {
 21+ /**
 22+ * Increment this when changing class structure
 23+ */
 24+ const GADGET_CLASS_VERSION = 5;
 25+
 26+ private $version = self::GADGET_CLASS_VERSION,
 27+ $scripts = array(),
 28+ $styles = array(),
 29+ $dependencies = array(),
 30+ $name,
 31+ $definition,
 32+ $resourceLoaded = false,
 33+ $requiredRights = array(),
 34+ $onByDefault = false,
 35+ $category,
 36+ $preferences = null;
 37+
 38+
 39+ //Syntax specifications of preference description language
 40+ private static $prefsDescriptionSpecifications = array(
 41+ 'boolean' => array(
 42+ 'default' => array(
 43+ 'isMandatory' => true,
 44+ 'checker' => 'is_bool'
 45+ ),
 46+ 'label' => array(
 47+ 'isMandatory' => true,
 48+ 'checker' => 'is_string'
 49+ )
 50+ ),
 51+ 'string' => array(
 52+ 'default' => array(
 53+ 'isMandatory' => true,
 54+ 'checker' => 'is_string'
 55+ ),
 56+ 'label' => array(
 57+ 'isMandatory' => true,
 58+ 'checker' => 'is_string'
 59+ ),
 60+ 'required' => array(
 61+ 'isMandatory' => false,
 62+ 'checker' => 'is_bool'
 63+ ),
 64+ 'minlength' => array(
 65+ 'isMandatory' => false,
 66+ 'checker' => 'is_integer'
 67+ ),
 68+ 'maxlength' => array(
 69+ 'isMandatory' => false,
 70+ 'checker' => 'is_integer'
 71+ )
 72+ ),
 73+ 'number' => array(
 74+ 'default' => array(
 75+ 'isMandatory' => true,
 76+ 'checker' => 'Gadget::isFloatOrInt'
 77+ ),
 78+ 'label' => array(
 79+ 'isMandatory' => true,
 80+ 'checker' => 'is_string'
 81+ ),
 82+ 'required' => array(
 83+ 'isMandatory' => false,
 84+ 'checker' => 'is_bool'
 85+ ),
 86+ 'integer' => array(
 87+ 'isMandatory' => false,
 88+ 'checker' => 'is_bool'
 89+ ),
 90+ 'min' => array(
 91+ 'isMandatory' => false,
 92+ 'checker' => 'Gadget::isFloatOrInt'
 93+ ),
 94+ 'max' => array(
 95+ 'isMandatory' => false,
 96+ 'checker' => 'Gadget::isFloatOrInt'
 97+ )
 98+ ),
 99+ 'select' => array(
 100+ 'default' => array(
 101+ 'isMandatory' => true
 102+ ),
 103+ 'label' => array(
 104+ 'isMandatory' => true,
 105+ 'checker' => 'is_string'
 106+ ),
 107+ 'options' => array(
 108+ 'isMandatory' => true,
 109+ 'checker' => 'is_array'
 110+ )
 111+ )
 112+ );
 113+
 114+ //Type-specific checkers for finer validation
 115+ private static $typeCheckers = array(
 116+ 'string' => 'Gadget::checkStringOptionDefinition',
 117+ 'number' => 'Gadget::checkNumberOptionDefinition',
 118+ 'select' => 'Gadget::checkSelectOptionDefinition'
 119+ );
 120+
 121+ //Further checks for 'string' options
 122+ private static function checkStringOptionDefinition( $option ) {
 123+ if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) {
 124+ return false;
 125+ }
 126+
 127+ if ( isset( $option['maxlength'] ) && $option['maxlength'] <= 0 ) {
 128+ return false;
 129+ }
 130+
 131+ if ( isset( $option['minlength']) && isset( $option['maxlength'] ) ) {
 132+ if ( $option['minlength'] > $option['maxlength'] ) {
 133+ return false;
 134+ }
 135+ }
 136+
 137+ //$default must pass validation, too
 138+ $required = isset( $option['required'] ) ? $option['required'] : true;
 139+ $default = $option['default'];
 140+
 141+ if ( $required === false && $default === '' ) {
 142+ return true; //empty string is good, skip other checks
 143+ }
 144+
 145+ $len = strlen( $default );
 146+ if ( isset( $option['minlength'] ) && $len < $option['minlength'] ) {
 147+ return false;
 148+ }
 149+ if ( isset( $option['maxlength'] ) && $len > $option['maxlength'] ) {
 150+ return false;
 151+ }
 152+
 153+ return true;
 154+ }
 155+
 156+ private static function isFloatOrInt( $param ) {
 157+ return is_float( $param ) || is_int( $param );
 158+ }
 159+
 160+ //Further checks for 'number' options
 161+ private static function checkNumberOptionDefinition( $option ) {
 162+ if ( isset( $option['integer'] ) && $option['integer'] === true ) {
 163+ //Check if 'min', 'max' and 'default' are integers (if given)
 164+ if ( intval( $option['default'] ) != $option['default'] ) {
 165+ return false;
 166+ }
 167+ if ( isset( $option['min'] ) && intval( $option['min'] ) != $option['min'] ) {
 168+ return false;
 169+ }
 170+ if ( isset( $option['max'] ) && intval( $option['max'] ) != $option['max'] ) {
 171+ return false;
 172+ }
 173+ }
 174+
 175+ //validate $option['default']
 176+ $default = $option['default'];
 177+
 178+ if ( isset( $option['min'] ) && $default < $option['min'] ) {
 179+ return false;
 180+ }
 181+ if ( isset( $option['max'] ) && $default > $option['max'] ) {
 182+ return false;
 183+ }
 184+
 185+ return true;
 186+ }
 187+
 188+ private static function checkSelectOptionDefinition( $option ) {
 189+ $options = $option['options'];
 190+
 191+ foreach ( $options as $opt => $optVal ) {
 192+ //Correct value for $optVal are NULL, boolean, integer, float or string
 193+ if ( $optVal !== NULL &&
 194+ !is_bool( $optVal ) &&
 195+ !is_int( $optVal ) &&
 196+ !is_float( $optVal ) &&
 197+ !is_string( $optVal ) )
 198+ {
 199+ return false;
 200+ }
 201+ }
 202+
 203+ $values = array_values( $options );
 204+
 205+ $default = $option['default'];
 206+
 207+ //Checks that $default is one of the option values
 208+ if ( !in_array( $default, $values, true ) ){
 209+ return false;
 210+ }
 211+
 212+ return true;
 213+ }
 214+
 215+ /**
 216+ * Creates an instance of this class from definition in MediaWiki:Gadgets-definition
 217+ * @param $definition String: Gadget definition
 218+ * @return Mixed: Instance of Gadget class or false if $definition is invalid
 219+ */
 220+ public static function newFromDefinition( $definition ) {
 221+ $m = array();
 222+ if ( !preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/', $definition, $m ) ) {
 223+ return false;
 224+ }
 225+ //NOTE: the gadget name is used as part of the name of a form field,
 226+ // and must follow the rules defined in http://www.w3.org/TR/html4/types.html#type-cdata
 227+ // Also, title-normalization applies.
 228+ $gadget = new Gadget();
 229+ $gadget->name = trim( str_replace(' ', '_', $m[1] ) );
 230+ $gadget->definition = $definition;
 231+ $options = trim( $m[2], ' []' );
 232+ foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) {
 233+ $arr = preg_split( '/\s*=\s*/', $option, 2 );
 234+ $option = $arr[0];
 235+ if ( isset( $arr[1] ) ) {
 236+ $params = explode( ',', $arr[1] );
 237+ $params = array_map( 'trim', $params );
 238+ } else {
 239+ $params = array();
 240+ }
 241+ switch ( $option ) {
 242+ case 'ResourceLoader':
 243+ $gadget->resourceLoaded = true;
 244+ break;
 245+ case 'dependencies':
 246+ $gadget->dependencies = $params;
 247+ break;
 248+ case 'rights':
 249+ $gadget->requiredRights = $params;
 250+ break;
 251+ case 'default':
 252+ $gadget->onByDefault = true;
 253+ break;
 254+ }
 255+ }
 256+ foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
 257+ $page = "Gadget-$page";
 258+ if ( preg_match( '/\.js/', $page ) ) {
 259+ $gadget->scripts[] = $page;
 260+ } elseif ( preg_match( '/\.css/', $page ) ) {
 261+ $gadget->styles[] = $page;
 262+ }
 263+ }
 264+ return $gadget;
 265+ }
 266+
 267+ /**
 268+ * @return String: Gadget name
 269+ */
 270+ public function getName() {
 271+ return $this->name;
 272+ }
 273+
 274+ /**
 275+ * @return String: Gadget description parsed into HTML
 276+ */
 277+ public function getDescription() {
 278+ return wfMessage( "gadget-{$this->getName()}" )->parse();
 279+ }
 280+
 281+ /**
 282+ * @return String: Wikitext of gadget description
 283+ */
 284+ public function getRawDescription() {
 285+ return wfMessage( "gadget-{$this->getName()}" )->plain();
 286+ }
 287+
 288+ /**
 289+ * @return String: Name of category (aka section) our gadget belongs to. Empty string if none.
 290+ */
 291+ public function getCategory() {
 292+ return $this->category;
 293+ }
 294+
 295+ /**
 296+ * @return String: Name of ResourceLoader module for this gadget
 297+ */
 298+ public function getModuleName() {
 299+ return "ext.gadget.{$this->name}";
 300+ }
 301+
 302+ /**
 303+ * Checks whether this is an instance of an older version of this class deserialized from cache
 304+ * @return Boolean
 305+ */
 306+ public function isOutdated() {
 307+ return $this->version != self::GADGET_CLASS_VERSION;
 308+ }
 309+
 310+ /**
 311+ * Checks whether this gadget is enabled for given user
 312+ *
 313+ * @param $user User: user to check against
 314+ * @return Boolean
 315+ */
 316+ public function isEnabled( $user ) {
 317+ return (bool)$user->getOption( "gadget-{$this->name}", $this->onByDefault );
 318+ }
 319+
 320+ /**
 321+ * Checks whether given user has permissions to use this gadget
 322+ *
 323+ * @param $user User: user to check against
 324+ * @return Boolean
 325+ */
 326+ public function isAllowed( $user ) {
 327+ return count( array_intersect( $this->requiredRights, $user->getRights() ) ) == count( $this->requiredRights );
 328+ }
 329+
 330+ /**
 331+ * @return Boolean: Whether this gadget is on by default for everyone (but can be disabled in preferences)
 332+ */
 333+ public function isOnByDefault() {
 334+ return $this->onByDefault;
 335+ }
 336+
 337+ /**
 338+ * @return Boolean: Whether all of this gadget's JS components support ResourceLoader
 339+ */
 340+ public function supportsResourceLoader() {
 341+ return $this->resourceLoaded;
 342+ }
 343+
 344+ /**
 345+ * @return Boolean: Whether this gadget has resources that can be loaded via ResourceLoader
 346+ */
 347+ public function hasModule() {
 348+ return count( $this->styles )
 349+ + ( $this->supportsResourceLoader() ? count( $this->scripts ) : 0 )
 350+ > 0;
 351+ }
 352+
 353+ /**
 354+ * @return String: Definition for this gadget from MediaWiki:gadgets-definition
 355+ */
 356+ public function getDefinition() {
 357+ return $this->definition;
 358+ }
 359+
 360+ /**
 361+ * @return Array: Array of pages with JS not prefixed with namespace
 362+ */
 363+ public function getScripts() {
 364+ return $this->scripts;
 365+ }
 366+
 367+ /**
 368+ * @return Array: Array of pages with CSS not prefixed with namespace
 369+ */
 370+ public function getStyles() {
 371+ return $this->styles;
 372+ }
 373+
 374+ /**
 375+ * @return Array: Array of all of this gadget's resources
 376+ */
 377+ public function getScriptsAndStyles() {
 378+ return array_merge( $this->scripts, $this->styles );
 379+ }
 380+
 381+ /**
 382+ * Returns module for ResourceLoader, see getModuleName() for its name.
 383+ * If our gadget has no scripts or styles suitable for RL, false will be returned.
 384+ * @return Mixed: GadgetResourceLoaderModule or false
 385+ */
 386+ public function getModule() {
 387+ $pages = array();
 388+ foreach( $this->styles as $style ) {
 389+ $pages['MediaWiki:' . $style] = array( 'type' => 'style' );
 390+ }
 391+ if ( $this->supportsResourceLoader() ) {
 392+ foreach ( $this->scripts as $script ) {
 393+ $pages['MediaWiki:' . $script] = array( 'type' => 'script' );
 394+ }
 395+ }
 396+ if ( !count( $pages ) ) {
 397+ return null;
 398+ }
 399+ return new GadgetResourceLoaderModule( $pages, $this->dependencies, $this );
 400+ }
 401+
 402+ /**
 403+ * Returns list of scripts that don't support ResourceLoader
 404+ * @return Array
 405+ */
 406+ public function getLegacyScripts() {
 407+ if ( $this->supportsResourceLoader() ) {
 408+ return array();
 409+ }
 410+ return $this->scripts;
 411+ }
 412+
 413+ /**
 414+ * Returns names of resources this gadget depends on
 415+ * @return Array
 416+ */
 417+ public function getDependencies() {
 418+ return $this->dependencies;
 419+ }
 420+
 421+ /**
 422+ * Returns array of permissions required by this gadget
 423+ * @return Array
 424+ */
 425+ public function getRequiredRights() {
 426+ return $this->requiredRights;
 427+ }
 428+
 429+ /**
 430+ * Loads and returns a list of all gadgets
 431+ * @return Mixed: Array of gadgets or false
 432+ */
 433+ public static function loadList() {
 434+ static $gadgets = null;
 435+
 436+ if ( $gadgets !== null ) return $gadgets;
 437+
 438+ wfProfileIn( __METHOD__ );
 439+ $struct = self::loadStructuredList();
 440+ if ( !$struct ) {
 441+ $gadgets = $struct;
 442+ wfProfileOut( __METHOD__ );
 443+ return $gadgets;
 444+ }
 445+
 446+ $gadgets = array();
 447+ foreach ( $struct as $section => $entries ) {
 448+ $gadgets = array_merge( $gadgets, $entries );
 449+ }
 450+ wfProfileOut( __METHOD__ );
 451+
 452+ return $gadgets;
 453+ }
 454+
 455+ /**
 456+ * Checks whether gadget list from cache can be used.
 457+ * @return Boolean
 458+ */
 459+ private static function isValidList( $gadgets ) {
 460+ if ( !is_array( $gadgets ) ) return false;
 461+ // Check if we have 1) array of gadgets 2) the gadgets are up to date
 462+ // One check is enough
 463+ foreach ( $gadgets as $section => $list ) {
 464+ foreach ( $list as $g ) {
 465+ if ( !( $g instanceof Gadget ) || $g->isOutdated() ) {
 466+ return false;
 467+ } else {
 468+ return true;
 469+ }
 470+ }
 471+ }
 472+ return true; // empty array
 473+ }
 474+
 475+
 476+ /**
 477+ * Loads list of gadgets and returns it as associative array of sections with gadgets
 478+ * e.g. array( 'sectionnname1' => array( $gadget1, $gadget2),
 479+ * 'sectionnname2' => array( $gadget3 ) );
 480+ * @param $forceNewText String: New text of MediaWiki:gadgets-sdefinition. If specified, will
 481+ * force a purge of cache and recreation of the gadget list.
 482+ * @return Mixed: Array or false
 483+ */
 484+ public static function loadStructuredList( $forceNewText = null ) {
 485+ global $wgMemc;
 486+
 487+ static $gadgets = null;
 488+
 489+ if ( $gadgets !== null && $forceNewText === null ) {
 490+ return $gadgets;
 491+ }
 492+
 493+ wfProfileIn( __METHOD__ );
 494+
 495+ $user = RequestContext::getMain()->getUser();
 496+ if ( $user->isLoggedIn() ) {
 497+ //Force loading user options
 498+ //HACK: this may lead to loadStructuredList being recursively called.
 499+ $user->getOptions();
 500+
 501+ //Check again, loadStructuredList may have been called from UserLoadOptions hook handler;
 502+ //in that case, we should just return current value instead of rebuilding the list again.
 503+ //TODO: is there a better design?
 504+ if ( $gadgets !== null && $forceNewText === null ) {
 505+ wfProfileOut( __METHOD__ );
 506+ return $gadgets;
 507+ }
 508+ }
 509+
 510+ $key = wfMemcKey( 'gadgets-definition', self::GADGET_CLASS_VERSION );
 511+
 512+ if ( $forceNewText === null ) {
 513+ //cached?
 514+ $gadgets = $wgMemc->get( $key );
 515+ if ( self::isValidList( $gadgets ) ) {
 516+ wfProfileOut( __METHOD__ );
 517+ return $gadgets;
 518+ }
 519+
 520+ $g = wfMsgForContentNoTrans( "gadgets-definition" );
 521+ if ( wfEmptyMsg( "gadgets-definition", $g ) ) {
 522+ $gadgets = false;
 523+ wfProfileOut( __METHOD__ );
 524+ return $gadgets;
 525+ }
 526+ } else {
 527+ $g = $forceNewText;
 528+ }
 529+
 530+ $g = preg_replace( '/<!--.*-->/s', '', $g );
 531+ $g = preg_split( '/(\r\n|\r|\n)+/', $g );
 532+
 533+ $gadgets = array();
 534+ $section = '';
 535+
 536+ foreach ( $g as $line ) {
 537+ $m = array();
 538+ if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
 539+ $section = $m[1];
 540+ }
 541+ else {
 542+ $gadget = self::newFromDefinition( $line );
 543+ if ( $gadget ) {
 544+ $gadgets[$section][$gadget->getName()] = $gadget;
 545+ $gadget->category = $section;
 546+ }
 547+ }
 548+ }
 549+
 550+ //cache for a while. gets purged automatically when MediaWiki:Gadgets-definition is edited
 551+ $wgMemc->set( $key, $gadgets, 60*60*24 );
 552+ $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition';
 553+ wfDebug( __METHOD__ . ": $source parsed, cache entry $key updated\n");
 554+ wfProfileOut( __METHOD__ );
 555+
 556+ return $gadgets;
 557+ }
 558+
 559+ //TODO: put the following static methods somewhere else
 560+
 561+ //Checks if the given description of the preferences is valid
 562+ public static function isGadgetPrefsDescriptionValid( &$prefsDescriptionJson ) {
 563+ $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true );
 564+
 565+ if ( $prefsDescription === null || !isset( $prefsDescription['fields'] ) ) {
 566+ return false;
 567+ }
 568+
 569+ //Count of mandatory members for each type
 570+ $mandatoryCount = array();
 571+ foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) {
 572+ $mandatoryCount[$type] = 0;
 573+ foreach ( $typeSpec as $fieldName => $fieldSpec ) {
 574+ if ( $fieldSpec['isMandatory'] === true ) {
 575+ ++$mandatoryCount[$type];
 576+ }
 577+ }
 578+ }
 579+
 580+ //TODO: validation of members other than $prefs['fields']
 581+
 582+ foreach ( $prefsDescription['fields'] as $option => $optionDefinition ) {
 583+
 584+ //Check if 'type' is set and valid
 585+ if ( !isset( $optionDefinition['type'] ) ) {
 586+ return false;
 587+ }
 588+
 589+ $type = $optionDefinition['type'];
 590+
 591+ if ( !isset( self::$prefsDescriptionSpecifications[$type] ) ) {
 592+ return false;
 593+ }
 594+
 595+ //TODO: check $option name compliance
 596+
 597+ //Check if all fields satisfy specification
 598+ $typeSpec = self::$prefsDescriptionSpecifications[$type];
 599+ $count = 0; //count of present mandatory members
 600+ foreach ( $optionDefinition as $fieldName => $fieldValue ) {
 601+
 602+ if ( $fieldName == 'type' ) {
 603+ continue; //'type' must not be checked
 604+ }
 605+
 606+ if ( !isset( $typeSpec[$fieldName] ) ) {
 607+ return false;
 608+ }
 609+
 610+ if ( $typeSpec[$fieldName]['isMandatory'] ) {
 611+ ++$count;
 612+ }
 613+
 614+ if ( isset( $typeSpec[$fieldName]['checker'] ) ) {
 615+ $checker = $typeSpec[$fieldName]['checker'];
 616+ if ( !call_user_func( $checker, $fieldValue ) ) {
 617+ return false;
 618+ }
 619+ }
 620+ }
 621+
 622+ if ( $count != $mandatoryCount[$type] ) {
 623+ return false; //not all mandatory members are given
 624+ }
 625+
 626+ if ( isset( self::$typeCheckers[$type] ) ) {
 627+ //Call type-specific checker for finer validation
 628+ if ( !call_user_func( self::$typeCheckers[$type], $optionDefinition ) ) {
 629+ return false;
 630+ }
 631+ }
 632+ }
 633+
 634+ return true;
 635+ }
 636+
 637+ /**
 638+ * Gets description of preferences for this gadget.
 639+ *
 640+ * @return Mixed null if the gadget exists but doesn't have any preferences or if provided ones are not valid,
 641+ * an array with the description of preferences otherwise.
 642+ */
 643+ public function getPrefsDescription() {
 644+ $prefsDescriptionMsg = "Gadget-{$this->name}.preferences";
 645+
 646+ //TODO: use cache
 647+
 648+ $prefsDescriptionJson = wfMsgForContentNoTrans( $prefsDescriptionMsg );
 649+ if ( wfEmptyMsg( $prefsDescriptionMsg, $prefsDescriptionJson ) ||
 650+ !self::isGadgetPrefsDescriptionValid( $prefsDescriptionJson ) )
 651+ {
 652+ return null;
 653+ }
 654+
 655+ return FormatJson::decode( $prefsDescriptionJson, true );
 656+ }
 657+
 658+ //Check if a preference is valid, according to description
 659+ //NOTE: we pass both $prefs and $prefName (instead of just $prefs[$prefName])
 660+ // to allow checking for null.
 661+ private static function checkSinglePref( $prefDescription, $prefs, $prefName ) {
 662+
 663+ //isset( $prefs[$prefName] ) would return false for null values
 664+ if ( !array_key_exists( $prefName, $prefs ) ) {
 665+ return false;
 666+ }
 667+
 668+ $pref = $prefs[$prefName];
 669+
 670+ switch ( $prefDescription['type'] ) {
 671+ case 'boolean':
 672+ return is_bool( $pref );
 673+ case 'string':
 674+ if ( !is_string( $pref ) ) {
 675+ return false;
 676+ }
 677+
 678+ $len = strlen( $pref );
 679+
 680+ //Checks the "required" option, if present
 681+ $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
 682+ if ( $required === true && $len == 0 ) {
 683+ return false;
 684+ } elseif ( $required === false && $len == 0 ) {
 685+ return true; //overriding 'minlength'
 686+ }
 687+
 688+ //Checks the "minlength" option, if present
 689+ $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0;
 690+ if ( $len < $minlength ){
 691+ return false;
 692+ }
 693+
 694+ //Checks the "maxlength" option, if present
 695+ $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here?
 696+ if ( $len > $maxlength ){
 697+ return false;
 698+ }
 699+
 700+ return true;
 701+ case 'number':
 702+ if ( !is_float( $pref ) && !is_int( $pref ) && $pref !== null ) {
 703+ return false;
 704+ }
 705+
 706+ $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
 707+ if ( $required === false && $pref === null ) {
 708+ return true;
 709+ }
 710+
 711+ if ( $pref === null ) {
 712+ return false; //$required === true, so null is not acceptable
 713+ }
 714+
 715+ $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false;
 716+
 717+ if ( $integer === true && intval( $pref ) != $pref ) {
 718+ return false; //not integer
 719+ }
 720+
 721+ if ( isset( $prefDescription['min'] ) ) {
 722+ $min = $prefDescription['min'];
 723+ if ( $pref < $min ) {
 724+ return false; //value below minimum
 725+ }
 726+ }
 727+
 728+ if ( isset( $prefDescription['max'] ) ) {
 729+ $max = $prefDescription['max'];
 730+ if ( $pref > $max ) {
 731+ return false; //value above maximum
 732+ }
 733+ }
 734+
 735+ return true;
 736+ case 'select':
 737+ $values = array_values( $prefDescription['options'] );
 738+ return in_array( $pref, $values, true );
 739+ default:
 740+ return false; //unexisting type
 741+ }
 742+ }
 743+
 744+ /**
 745+ * Checks if $prefs is an array of preferences that passes validation
 746+ *
 747+ * @param $prefsDescription Array: the preferences description to use.
 748+ * @param &$prefs Array: reference of the array of preferences to check.
 749+ *
 750+ * @return boolean true if $prefs passes validation against $prefsDescription, false otherwise.
 751+ */
 752+ public static function checkPrefsAgainstDescription( $prefsDescription, $prefs ) {
 753+ foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
 754+ if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
 755+ return false;
 756+ }
 757+ }
 758+ return true;
 759+ }
 760+
 761+ /**
 762+ * Fixes $prefs so that it matches the description given by $prefsDescription.
 763+ * All values of $prefs that fail validation are replaced with default values.
 764+ *
 765+ * @param $prefsDescription Array: the preferences description to use.
 766+ * @param &$prefs Array: reference of the array of preferences to match.
 767+ */
 768+ public static function matchPrefsWithDescription( $prefsDescription, &$prefs ) {
 769+ //Remove unexisting preferences from $prefs
 770+ foreach ( $prefs as $prefName => $value ) {
 771+ if ( !isset( $prefsDescription['fields'][$prefName] ) ) {
 772+ unset( $prefs[$prefName] );
 773+ }
 774+ }
 775+
 776+ //Fix preferences that fail validation
 777+ foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
 778+ if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
 779+ $prefs[$prefName] = $prefDescription['default'];
 780+ }
 781+ }
 782+ }
 783+
 784+ /**
 785+ * Returns current user's preferences for this gadget.
 786+ *
 787+ * @return Mixed the array of preferences if they have been set, null otherwise.
 788+ */
 789+ public function getPrefs() {
 790+ return $this->preferences;
 791+ }
 792+
 793+ /**
 794+ * Sets current user's preferences for this gadget, after validating them.
 795+ *
 796+ * @param $prefs Array: the array of preferences.
 797+ * @param $savePrefs boolean: if true, preferences are also saved back to the Database.
 798+ *
 799+ * @return boolean: true if validation is passed, false otherwise.
 800+ */
 801+ public function setPrefs( $prefs, $savePrefs = false ) {
 802+
 803+ if ( is_string( $prefs ) ) {
 804+ $prefs = FormatJson::decode( $prefs, true );
 805+ }
 806+
 807+ if ( $prefs === null || !is_array( $prefs ) ) {
 808+ throw new MWException( __METHOD__ . ': $prefs must be an array or valid JSON' );
 809+ }
 810+
 811+ $prefsDescription = $this->getPrefsDescription();
 812+
 813+ if ( $prefsDescription === null ) {
 814+ return false; //nothing to save
 815+ }
 816+
 817+ if ( !self::checkPrefsAgainstDescription( $prefsDescription, $prefs ) ) {
 818+ return false; //validation failed
 819+ }
 820+
 821+ $this->preferences = $prefs;
 822+
 823+ if ( $savePrefs ) {
 824+ $user = RequestContext::getMain()->getUser();
 825+ $user->saveSettings();
 826+ }
 827+ return true;
 828+ }
 829+}
Property changes on: branches/salvatoreingala/Gadgets/backend/Gadget.php
___________________________________________________________________
Added: svn:eol-style
1830 + native
Index: branches/salvatoreingala/Gadgets/backend/GadgetHooks.php
@@ -0,0 +1,275 @@
 2+<?php
 3+
 4+/**
 5+ * Gadgets extension - lets users select custom javascript gadgets
 6+ *
 7+ *
 8+ * For more info see http://mediawiki.org/wiki/Extension:Gadgets
 9+ *
 10+ * @file
 11+ * @ingroup Extensions
 12+ * @author Daniel Kinzler, brightbyte.de
 13+ * @copyright © 2007 Daniel Kinzler
 14+ * @license GNU General Public Licence 2.0 or later
 15+ */
 16+
 17+class GadgetHooks {
 18+
 19+ /**
 20+ * ArticleSaveComplete hook handler.
 21+ *
 22+ * @param $article Article
 23+ * @param $user User
 24+ * @param $text String: New page text
 25+ */
 26+ public static function articleSaveComplete( $article, $user, $text ) {
 27+ //update cache if MediaWiki:Gadgets-definition was edited
 28+ $title = $article->mTitle;
 29+ if( $title->getNamespace() == NS_MEDIAWIKI && $title->getText() == 'Gadgets-definition' ) {
 30+ Gadget::loadStructuredList( $text );
 31+ }
 32+ return true;
 33+ }
 34+
 35+ /**
 36+ * GetPreferences hook handler.
 37+ * @param $user User
 38+ * @param $preferences Array: Preference descriptions
 39+ */
 40+ public static function getPreferences( $user, &$preferences ) {
 41+ $gadgets = Gadget::loadStructuredList();
 42+ if (!$gadgets) return true;
 43+
 44+ $options = array();
 45+ $default = array();
 46+ foreach( $gadgets as $section => $thisSection ) {
 47+ $available = array();
 48+ foreach( $thisSection as $gadget ) {
 49+ if ( $gadget->isAllowed( $user ) ) {
 50+ $gname = $gadget->getName();
 51+ $available[$gadget->getDescription()] = $gname;
 52+ if ( $gadget->isEnabled( $user ) ) {
 53+ $default[] = $gname;
 54+ }
 55+ }
 56+ }
 57+ if ( $section !== '' ) {
 58+ $section = wfMsgExt( "gadget-section-$section", 'parseinline' );
 59+ if ( count ( $available ) ) {
 60+ $options[$section] = $available;
 61+ }
 62+ } else {
 63+ $options = array_merge( $options, $available );
 64+ }
 65+ }
 66+
 67+ $preferences['gadgets-intro'] =
 68+ array(
 69+ 'type' => 'info',
 70+ 'label' => '&#160;',
 71+ 'default' => Xml::tags( 'tr', array(),
 72+ Xml::tags( 'td', array( 'colspan' => 2 ),
 73+ wfMsgExt( 'gadgets-prefstext', 'parse' ) ) ),
 74+ 'section' => 'gadgets',
 75+ 'raw' => 1,
 76+ 'rawrow' => 1,
 77+ );
 78+
 79+ $preferences['gadgets'] =
 80+ array(
 81+ 'type' => 'multiselect',
 82+ 'options' => $options,
 83+ 'section' => 'gadgets',
 84+ 'label' => '&#160;',
 85+ 'prefix' => 'gadget-',
 86+ 'default' => $default,
 87+ );
 88+
 89+ return true;
 90+ }
 91+
 92+ /**
 93+ * ResourceLoaderRegisterModules hook handler.
 94+ * @param $resourceLoader ResourceLoader
 95+ */
 96+ public static function registerModules( &$resourceLoader ) {
 97+ $gadgets = Gadget::loadList();
 98+ if ( !$gadgets ) {
 99+ return true;
 100+ }
 101+ foreach ( $gadgets as $g ) {
 102+ $module = $g->getModule();
 103+ if ( $module ) {
 104+ $resourceLoader->register( $g->getModuleName(), $module );
 105+ }
 106+ }
 107+ return true;
 108+ }
 109+
 110+ /**
 111+ * BeforePageDisplay hook handler.
 112+ * @param $out OutputPage
 113+ */
 114+ public static function beforePageDisplay( $out ) {
 115+ global $wgUser;
 116+
 117+ wfProfileIn( __METHOD__ );
 118+
 119+ //tweaks in Special:Preferences
 120+ if ( $out->getTitle()->isSpecial( 'Preferences' ) ) {
 121+ $out->addModules( 'ext.gadgets.preferences' );
 122+ }
 123+
 124+ $gadgets = Gadget::loadList();
 125+ if ( !$gadgets ) {
 126+ wfProfileOut( __METHOD__ );
 127+ return true;
 128+ }
 129+
 130+ $lb = new LinkBatch();
 131+ $lb->setCaller( __METHOD__ );
 132+ $pages = array();
 133+
 134+ foreach ( $gadgets as $gadget ) {
 135+ if ( $gadget->isEnabled( $wgUser ) && $gadget->isAllowed( $wgUser ) ) {
 136+ if ( $gadget->hasModule() ) {
 137+ $out->addModules( $gadget->getModuleName() );
 138+ }
 139+ foreach ( $gadget->getLegacyScripts() as $page ) {
 140+ $lb->add( NS_MEDIAWIKI, $page );
 141+ $pages[] = $page;
 142+ }
 143+ }
 144+ }
 145+
 146+ $lb->execute( __METHOD__ );
 147+
 148+ $done = array();
 149+ foreach ( $pages as $page ) {
 150+ if ( isset( $done[$page] ) ) continue;
 151+ $done[$page] = true;
 152+ self::applyScript( $page, $out );
 153+ }
 154+ wfProfileOut( __METHOD__ );
 155+
 156+ return true;
 157+ }
 158+
 159+ /**
 160+ * UserLoadOptions hook handler.
 161+ * @param $user
 162+ * @param &$options
 163+ */
 164+ public static function userLoadOptions( $user, &$options ) {
 165+ //Only if it's current user
 166+ $curUser = RequestContext::getMain()->getUser();
 167+ if ( $curUser->getID() !== $user->getID() ) {
 168+ return true;
 169+ }
 170+
 171+ //Find out all existing gadget preferences and save them in a map
 172+ $preferencesCache = array();
 173+ foreach ( $options as $option => $value ) {
 174+ $m = array();
 175+ if ( preg_match( '/gadget-([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)-config/', $option, $m ) ) {
 176+ $gadgetName = $m[1];
 177+ wfSuppressWarnings();
 178+ $gadgetPrefs = unserialize( $value );
 179+ wfRestoreWarnings();
 180+ if ( $gadgetPrefs !== false ) {
 181+ $preferencesCache[$gadgetName] = $gadgetPrefs;
 182+ } else {
 183+ //should not happen; just in case
 184+ wfDebug( __METHOD__ . ": couldn't unserialize settings for gadget " .
 185+ "$gadgetName and user {$curUser->getID()}. Ignoring.\n" );
 186+ }
 187+ unset( $options[$option] );
 188+ }
 189+ }
 190+
 191+ //Record preferences for each gadget
 192+ $gadgets = Gadget::loadList();
 193+ foreach ( $gadgets as $gadget ) {
 194+ $prefsDescription = $gadget->getPrefsDescription();
 195+ if ( $prefsDescription !== null ) {
 196+ if ( isset( $preferencesCache[$gadget->getName()] ) ) {
 197+ $userPrefs = $preferencesCache[$gadget->getName()];
 198+ }
 199+
 200+ if ( !isset( $userPrefs ) ) {
 201+ $userPrefs = array(); //no saved prefs (or invalid entry in DB), use defaults
 202+ }
 203+
 204+ Gadget::matchPrefsWithDescription( $prefsDescription, $userPrefs );
 205+
 206+ $gadget->setPrefs( $userPrefs );
 207+ }
 208+ }
 209+
 210+ return true;
 211+ }
 212+
 213+ /**
 214+ * UserSaveOptions hook handler.
 215+ * @param $user
 216+ * @param &$options
 217+ */
 218+ public static function userSaveOptions( $user, &$options ) {
 219+ //Only if it's current user
 220+ $curUser = RequestContext::getMain()->getUser();
 221+ if ( $curUser->getID() !== $user->getID() ) {
 222+ return true;
 223+ }
 224+
 225+ //Reinsert gadget-*-config options, so they can be saved back
 226+ $gadgets = Gadget::loadList();
 227+
 228+ if ( !$gadgets ) {
 229+ return true;
 230+ }
 231+
 232+ foreach ( $gadgets as $gadget ) {
 233+ if ( $gadget->getPrefs() !== null ) {
 234+ //TODO: should remove prefs that equal their default
 235+
 236+ $prefsSerialized = serialize( $gadget->getPrefs() );
 237+ $options["gadget-{$gadget->getName()}-config"] = $prefsSerialized;
 238+ }
 239+ }
 240+
 241+ return true;
 242+ }
 243+
 244+ /**
 245+ * Adds one legacy script to output.
 246+ *
 247+ * @param $page String: Unprefixed page title
 248+ * @param $out OutputPage
 249+ */
 250+ private static function applyScript( $page, $out ) {
 251+ global $wgJsMimeType;
 252+
 253+ # bug 22929: disable gadgets on sensitive pages. Scripts loaded through the
 254+ # ResourceLoader handle this in OutputPage::getModules()
 255+ # TODO: make this extension load everything via RL, then we don't need to worry
 256+ # about any of this.
 257+ if( $out->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) < ResourceLoaderModule::ORIGIN_USER_SITEWIDE ){
 258+ return;
 259+ }
 260+
 261+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, $page );
 262+ if ( !$t ) return;
 263+
 264+ $u = $t->getLocalURL( 'action=raw&ctype=' . $wgJsMimeType );
 265+ $out->addScriptFile( $u, $t->getLatestRevID() );
 266+ }
 267+
 268+ /**
 269+ * UnitTestsList hook handler
 270+ * @param $files Array: List of extension test files
 271+ */
 272+ public static function unitTestsList( $files ) {
 273+ $files[] = dirname( dirname( __FILE__ ) ) . '/Gadgets_tests.php';
 274+ return true;
 275+ }
 276+}
Property changes on: branches/salvatoreingala/Gadgets/backend/GadgetHooks.php
___________________________________________________________________
Added: svn:eol-style
1277 + native
Index: branches/salvatoreingala/Gadgets/backend/GadgetResourceLoaderModule.php
@@ -0,0 +1,85 @@
 2+<?php
 3+
 4+/**
 5+ * Gadgets extension - lets users select custom javascript gadgets
 6+ *
 7+ *
 8+ * For more info see http://mediawiki.org/wiki/Extension:Gadgets
 9+ *
 10+ * @file
 11+ * @ingroup Extensions
 12+ * @author Daniel Kinzler, brightbyte.de
 13+ * @copyright © 2007 Daniel Kinzler
 14+ * @license GNU General Public Licence 2.0 or later
 15+ */
 16+
 17+/**
 18+ * Class representing a list of resources for one gadget
 19+ */
 20+class GadgetResourceLoaderModule extends ResourceLoaderWikiModule {
 21+ private $pages, $dependencies, $gadget;
 22+
 23+ /**
 24+ * Creates an instance of this class
 25+ * @param $pages Array: Associative array of pages in ResourceLoaderWikiModule-compatible
 26+ * format, for example:
 27+ * array(
 28+ * 'MediaWiki:Gadget-foo.js' => array( 'type' => 'script' ),
 29+ * 'MediaWiki:Gadget-foo.css' => array( 'type' => 'style' ),
 30+ * )
 31+ * @param $dependencies Array: Names of resources this module depends on
 32+ */
 33+ public function __construct( $pages, $dependencies, $gadget ) {
 34+ $this->pages = $pages;
 35+ $this->dependencies = $dependencies;
 36+ $this->gadget = $gadget;
 37+ }
 38+
 39+ /**
 40+ * Overrides the abstract function from ResourceLoaderWikiModule class
 41+ * @return Array: $pages passed to __construct()
 42+ */
 43+ protected function getPages( ResourceLoaderContext $context ) {
 44+ return $this->pages;
 45+ }
 46+
 47+ /**
 48+ * Overrides ResourceLoaderModule::getDependencies()
 49+ * @return Array: Names of resources this module depends on
 50+ */
 51+ public function getDependencies() {
 52+ return $this->dependencies;
 53+ }
 54+
 55+ public function getScript( ResourceLoaderContext $context ) {
 56+ $prefs = $this->gadget->getPrefs();
 57+
 58+ //Enclose gadget's code in a closure, with "this" bound to the
 59+ //configuration object (or to "window" for non-configurable gadgets)
 60+ $header = '(function(){';
 61+
 62+ //TODO: it may be nice add other metadata for the gadget
 63+ $boundObject = array( 'config' => $prefs );
 64+
 65+ if ( $prefs !== NULL ) {
 66+ //Bind configuration object to "this".
 67+ $footer = '}).' . Xml::encodeJsCall( 'apply',
 68+ array( $boundObject, array() )
 69+ ) . ';';
 70+ } else {
 71+ //Bind window to "this"
 72+ $footer = '}).apply( window, [] );';
 73+ }
 74+
 75+ return $header . parent::getScript( $context ) . $footer;
 76+ }
 77+
 78+
 79+ //TODO: should depend on last modification time of gadget's configuration page, also
 80+ public function getModifiedTime( ResourceLoaderContext $context ) {
 81+ $touched = RequestContext::getMain()->getUser()->getTouched();
 82+
 83+ return max( parent::getModifiedTime( $context ), wfTimestamp( TS_UNIX, $touched ) );
 84+ }
 85+}
 86+
Property changes on: branches/salvatoreingala/Gadgets/backend/GadgetResourceLoaderModule.php
___________________________________________________________________
Added: svn:eol-style
187 + native
Index: branches/salvatoreingala/Gadgets/api/ApiQueryGadgets.php
@@ -0,0 +1,208 @@
 2+<?php
 3+/**
 4+ * Created on 15 April 2011
 5+ * API for Gadgets extension
 6+ *
 7+ * This program is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * This program is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License along
 18+ * with this program; if not, write to the Free Software Foundation, Inc.,
 19+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 20+ * http://www.gnu.org/copyleft/gpl.html
 21+ */
 22+
 23+class ApiQueryGadgets extends ApiQueryBase {
 24+ private $props,
 25+ $category,
 26+ $neededNames,
 27+ $listAllowed,
 28+ $listEnabled;
 29+
 30+ public function __construct( $query, $moduleName ) {
 31+ parent::__construct( $query, $moduleName, 'ga' );
 32+ }
 33+
 34+ public function execute() {
 35+ $params = $this->extractRequestParams();
 36+ $this->props = array_flip( $params['prop'] );
 37+ $this->categories = isset( $params['categories'] )
 38+ ? array_flip( $params['categories'] )
 39+ : false;
 40+ $this->neededNames = isset( $params['names'] )
 41+ ? array_flip( $params['names'] )
 42+ : false;
 43+ $this->listAllowed = isset( $params['allowed'] ) && $params['allowed'];
 44+ $this->listEnabled = isset( $params['enabled'] ) && $params['enabled'];
 45+
 46+ $this->getMain()->setCacheMode( $this->listAllowed || $this->listEnabled
 47+ ? 'anon-public-user-private' : 'public' );
 48+
 49+ $this->applyList( $this->getList() );
 50+ }
 51+
 52+ private function getList() {
 53+ $gadgets = Gadget::loadStructuredList();
 54+
 55+ $result = array();
 56+ foreach ( $gadgets as $category => $list ) {
 57+ if ( $this->categories && !isset( $this->categories[$category] ) ) {
 58+ continue;
 59+ }
 60+ foreach ( $list as $g ) {
 61+ if ( $this->isNeeded( $g ) ) {
 62+ $result[] = $g;
 63+ }
 64+ }
 65+ }
 66+ return $result;
 67+ }
 68+
 69+ private function applyList( $gadgets ) {
 70+ $data = array();
 71+ $result = $this->getResult();
 72+
 73+ foreach ( $gadgets as $g ) {
 74+ $row = array();
 75+ if ( isset( $this->props['name'] ) ) {
 76+ $row['name'] = $g->getName();
 77+ }
 78+ if ( isset( $this->props['desc'] ) ) {
 79+ $row['desc'] = $g->getDescription();
 80+ }
 81+ if ( isset( $this->props['desc-raw'] ) ) {
 82+ $row['desc-raw'] = $g->getRawDescription();
 83+ }
 84+ if ( isset( $this->props['category'] ) ) {
 85+ $row['category'] = $g->getCategory();
 86+ }
 87+ if ( isset( $this->props['resourceloader'] ) && $g->supportsResourceLoader() ) {
 88+ $row['resourceloader'] = '';
 89+ }
 90+ if ( isset( $this->props['scripts'] ) ) {
 91+ $row['scripts'] = $g->getScripts();
 92+ $result->setIndexedTagName( $row['scripts'], 'script' );
 93+ }
 94+ if ( isset( $this->props['styles'] ) ) {
 95+ $row['styles'] = $g->getStyles();
 96+ $result->setIndexedTagName( $row['styles'], 'style' );
 97+ }
 98+ if ( isset( $this->props['dependencies'] ) ) {
 99+ $row['dependencies'] = $g->getDependencies();
 100+ $result->setIndexedTagName( $row['dependencies'], 'module' );
 101+ }
 102+ if ( isset( $this->props['rights'] ) ) {
 103+ $row['rights'] = $g->getRequiredRights();
 104+ $result->setIndexedTagName( $row['rights'], 'right' );
 105+ }
 106+ if ( isset( $this->props['default'] ) && $g->isOnByDefault() ) {
 107+ $row['default'] = '';
 108+ }
 109+ if ( isset( $this->props['definition'] ) ) {
 110+ $row['definition'] = $g->getDefinition();
 111+ }
 112+ $data[] = $row;
 113+ }
 114+ $result->setIndexedTagName( $data, 'gadget' );
 115+ $result->addValue( 'query', $this->getModuleName(), $data );
 116+ }
 117+
 118+ /**
 119+ *
 120+ */
 121+ private function isNeeded( Gadget $gadget ) {
 122+ global $wgUser;
 123+
 124+ return ( $this->neededNames === false || isset( $this->neededNames[$gadget->getName()] ) )
 125+ && ( !$this->listAllowed || $gadget->isAllowed( $wgUser ) )
 126+ && ( !$this->listEnabled || $gadget->isEnabled( $wgUser ) );
 127+ }
 128+
 129+ public function getAllowedParams() {
 130+ return array(
 131+ 'prop' => array(
 132+ ApiBase::PARAM_DFLT => 'name',
 133+ ApiBase::PARAM_ISMULTI => true,
 134+ ApiBase::PARAM_TYPE => array(
 135+ 'name',
 136+ 'desc',
 137+ 'desc-raw',
 138+ 'category',
 139+ 'resourceloader',
 140+ 'scripts',
 141+ 'styles',
 142+ 'dependencies',
 143+ 'rights',
 144+ 'default',
 145+ 'definition',
 146+ ),
 147+ ),
 148+ 'categories' => array(
 149+ ApiBase::PARAM_ISMULTI => true,
 150+ ApiBase::PARAM_TYPE => 'string',
 151+ ),
 152+ 'names' => array(
 153+ ApiBase::PARAM_TYPE => 'string',
 154+ ApiBase::PARAM_ISMULTI => true,
 155+ ),
 156+ 'allowed' => false,
 157+ 'enabled' => false,
 158+ );
 159+ }
 160+
 161+ public function getDescription() {
 162+ return 'Returns a list of gadgets used on this wiki';
 163+ }
 164+
 165+ public function getParamDescription() {
 166+ return array(
 167+ 'prop' => array(
 168+ 'What gadget information to get:',
 169+ ' name - Internal gadget name',
 170+ ' desc - Gadget description transformed into HTML (can be slow, use only if really needed)',
 171+ ' desc-raw - Gadget description in raw wikitext',
 172+ ' category - Internal name of a category gadget belongs to (empty if top-level gadget)',
 173+ ' resourceloader - Whether gadget supports ResourceLoader',
 174+ " scripts - List of gadget's scripts",
 175+ " styles - List of gadget's styles",
 176+ ' dependencies - List of ResourceLoader modules gadget depends on',
 177+ ' rights - List of rights required to use gadget, if any',
 178+ ' default - Whether gadget is enabled by default',
 179+ ' definition - Line from MediaWiki:Gadgets-definition used to define the gadget',
 180+ ),
 181+ 'categories' => 'Gadgets from what categories to retrieve',
 182+ 'names' => 'Name(s) of gadgets to retrieve',
 183+ 'allowed' => 'List only gadgets allowed to current user',
 184+ 'enabled' => 'List only gadgets enabled by current user',
 185+ );
 186+ }
 187+
 188+ protected function getExamples() {
 189+ $params = $this->getAllowedParams();
 190+ $allProps = implode( '|', $params['prop'][ApiBase::PARAM_TYPE] );
 191+ return array(
 192+ 'Get a list of gadgets along with their descriptions:',
 193+ ' api.php?action=query&list=gadgets&gaprop=name|desc',
 194+ 'Get a list of gadgets with all possble properties:',
 195+ " api.php?action=query&list=gadgets&gaprop=$allProps",
 196+ 'Get a list of gadgets belonging to caregory "foo":',
 197+ ' api.php?action=query&list=gadgets&gacategories=foo',
 198+ 'Get information about gadgets named "foo" and "bar":',
 199+ ' api.php?action=query&list=gadgets&ganames=foo|bar&gaprop=name|desc|category',
 200+ 'Get a list of gadgets enabled by current user:',
 201+ ' api.php?action=query&list=gadgets&gaenabled',
 202+ );
 203+ }
 204+
 205+ public function getVersion() {
 206+ return __CLASS__ . ': $Id$';
 207+ }
 208+
 209+}
Property changes on: branches/salvatoreingala/Gadgets/api/ApiQueryGadgets.php
___________________________________________________________________
Added: svn:keywords
1210 + Id
Added: svn:eol-style
2211 + native
Index: branches/salvatoreingala/Gadgets/api/ApiQueryGadgetCategories.php
@@ -0,0 +1,122 @@
 2+<?php
 3+/**
 4+ * Created on 16 April 2011
 5+ * API for Gadgets extension
 6+ *
 7+ * This program is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * This program is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License along
 18+ * with this program; if not, write to the Free Software Foundation, Inc.,
 19+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 20+ * http://www.gnu.org/copyleft/gpl.html
 21+ */
 22+
 23+class ApiQueryGadgetCategories extends ApiQueryBase {
 24+ private $props,
 25+ $neededNames;
 26+
 27+ public function __construct( $query, $moduleName ) {
 28+ parent::__construct( $query, $moduleName, 'gc' );
 29+ }
 30+
 31+ public function execute() {
 32+ $params = $this->extractRequestParams();
 33+ $this->props = array_flip( $params['prop'] );
 34+ $this->neededNames = isset( $params['names'] )
 35+ ? array_flip( $params['names'] )
 36+ : false;
 37+
 38+ $this->getMain()->setCacheMode( 'public' );
 39+
 40+ $this->getList();
 41+ }
 42+
 43+ private function getList() {
 44+ $data = array();
 45+ $result = $this->getResult();
 46+ $gadgets = Gadget::loadStructuredList();
 47+
 48+ foreach ( $gadgets as $category => $list ) {
 49+ if ( !$this->neededNames || isset( $this->neededNames[$category] ) ) {
 50+ $row = array();
 51+ if ( isset( $this->props['name'] ) ) {
 52+ $row['name'] = $category;
 53+ }
 54+ if ( $category !== "" ) {
 55+ if ( isset( $this->props['desc'] ) ) {
 56+ $row['desc'] = wfMessage( "gadget-section-$category" )->parse();
 57+ }
 58+ if ( isset( $this->props['desc-raw'] ) ) {
 59+ $row['desc-raw'] = wfMessage( "gadget-section-$category" )->plain();
 60+ }
 61+ }
 62+ if ( isset( $this->props['members'] ) ) {
 63+ $row['members'] = count( $list );
 64+ }
 65+ $data[] = $row;
 66+ }
 67+ }
 68+ $result->setIndexedTagName( $data, 'category' );
 69+ $result->addValue( 'query', $this->getModuleName(), $data );
 70+ }
 71+
 72+ public function getAllowedParams() {
 73+ return array(
 74+ 'prop' => array(
 75+ ApiBase::PARAM_DFLT => 'name',
 76+ ApiBase::PARAM_ISMULTI => true,
 77+ ApiBase::PARAM_TYPE => array(
 78+ 'name',
 79+ 'desc',
 80+ 'desc-raw',
 81+ 'members',
 82+ ),
 83+ ),
 84+ 'names' => array(
 85+ ApiBase::PARAM_TYPE => 'string',
 86+ ApiBase::PARAM_ISMULTI => true,
 87+ ),
 88+ );
 89+ }
 90+
 91+ public function getDescription() {
 92+ return 'Returns a list of gadget categories';
 93+ }
 94+
 95+ public function getParamDescription() {
 96+ return array(
 97+ 'prop' => array(
 98+ 'What gadget category information to get:',
 99+ ' name - Internal category name',
 100+ ' desc - Category description transformed into HTML (can be slow, use only if really needed)',
 101+ ' desc-raw - Category description in raw wikitext',
 102+ ' members - Number of gadgets in category',
 103+ ),
 104+ 'names' => 'Name(s) of gadgets to retrieve',
 105+ );
 106+ }
 107+
 108+ protected function getExamples() {
 109+ $params = $this->getAllowedParams();
 110+ $allProps = implode( '|', $params['prop'][ApiBase::PARAM_TYPE] );
 111+ return array(
 112+ 'Get a list of existing gadget categories:',
 113+ ' api.php?action=query&list=gadgetcategories',
 114+ 'Get all information about categories named "foo" and "bar":',
 115+ " api.php?action=query&list=gadgetcategories&gcnames=foo|bar&gcprop=$allProps",
 116+ );
 117+ }
 118+
 119+ public function getVersion() {
 120+ return __CLASS__ . ': $Id$';
 121+ }
 122+
 123+}
Property changes on: branches/salvatoreingala/Gadgets/api/ApiQueryGadgetCategories.php
___________________________________________________________________
Added: svn:keywords
1124 + Id
Added: svn:eol-style
2125 + native
Index: branches/salvatoreingala/Gadgets/api/ApiSetGadgetPrefs.php
@@ -0,0 +1,122 @@
 2+<?php
 3+
 4+/**
 5+ *
 6+ * API for setting Gadget's preferences
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License along
 19+ * with this program; if not, write to the Free Software Foundation, Inc.,
 20+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 21+ * http://www.gnu.org/copyleft/gpl.html
 22+ */
 23+
 24+class ApiSetGadgetPrefs extends ApiBase {
 25+
 26+ public function execute() {
 27+ $user = RequestContext::getMain()->getUser();
 28+
 29+ $params = $this->extractRequestParams();
 30+ //Check permissions
 31+ if ( !$user->isLoggedIn() ) {
 32+ $this->dieUsage( 'You must be logged-in to set gadget\'s preferences', 'notloggedin' );
 33+ }
 34+
 35+ //Check token
 36+ if ( !$user->matchEditToken( $params['token'] ) ) {
 37+ $this->dieUsageMsg( 'sessionfailure' );
 38+ }
 39+
 40+ $gadgetName = $params['gadget'];
 41+ $gadgets = Gadget::loadList();
 42+ $gadget = $gadgets && isset( $gadgets[$gadgetName] ) ? $gadgets[$gadgetName] : null;
 43+
 44+ if ( $gadget === null ) {
 45+ $this->dieUsage( 'Gadget not found', 'notfound' );
 46+ }
 47+
 48+ $prefsJson = $params['prefs'];
 49+ $prefs = FormatJson::decode( $prefsJson, true );
 50+
 51+ if ( !is_array( $prefs ) ) {
 52+ $this->dieUsage( 'The \'pref\' parameter must be valid JSON', 'notjson' );
 53+ }
 54+
 55+ $result = $gadget->setPrefs( $prefs, true );
 56+
 57+ if ( $result === true ) {
 58+ $this->getResult()->addValue(
 59+ null, $this->getModuleName(), array( 'result' => 'Success' ) );
 60+ } else {
 61+ $this->dieUsage( 'Invalid preferences', 'invalidprefs' );
 62+ }
 63+ }
 64+
 65+ public function mustBePosted() {
 66+ return true;
 67+ }
 68+
 69+ public function isWriteMode() {
 70+ return true;
 71+ }
 72+
 73+ public function getAllowedParams() {
 74+ return array(
 75+ 'gadget' => array(
 76+ ApiBase::PARAM_TYPE => 'string',
 77+ ApiBase::PARAM_REQUIRED => true
 78+ ),
 79+ 'prefs' => array(
 80+ ApiBase::PARAM_TYPE => 'string',
 81+ ApiBase::PARAM_REQUIRED => true
 82+ ),
 83+ 'token' => array(
 84+ ApiBase::PARAM_TYPE => 'string',
 85+ ApiBase::PARAM_REQUIRED => true
 86+ ),
 87+ );
 88+ }
 89+
 90+ public function getParamDescription() {
 91+ return array(
 92+ 'gadget' => 'The name of the gadget',
 93+ 'prefs' => 'The new preferences in JSON format',
 94+ 'token' => 'An edit token'
 95+ );
 96+ }
 97+
 98+ public function getDescription() {
 99+ return 'Allows user code to set preferences for gadgets';
 100+ }
 101+
 102+ public function getPossibleErrors() {
 103+ return array_merge( parent::getPossibleErrors(), array(
 104+ array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to get gadget\'s preferences' ),
 105+ array( 'sessionfailure' ),
 106+ array( 'code' => 'notfound', 'info' => 'Gadget not found' ),
 107+ array( 'code' => 'notjson', 'info' => 'The \'pref\' parameter must be valid JSON' ),
 108+ array( 'code' => 'invalidprefs', 'info' => 'Invalid preferences' ),
 109+ ) );
 110+ }
 111+
 112+ public function needsToken() {
 113+ return true;
 114+ }
 115+
 116+ public function getSalt() {
 117+ return '';
 118+ }
 119+
 120+ public function getVersion() {
 121+ return __CLASS__ . ': $Id$';
 122+ }
 123+}
Property changes on: branches/salvatoreingala/Gadgets/api/ApiSetGadgetPrefs.php
___________________________________________________________________
Added: svn:keywords
1124 + Id
Added: svn:eol-style
2125 + native
Index: branches/salvatoreingala/Gadgets/api/ApiGetGadgetPrefs.php
@@ -0,0 +1,93 @@
 2+<?php
 3+
 4+/**
 5+ *
 6+ * API for setting Gadget's preferences
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License along
 19+ * with this program; if not, write to the Free Software Foundation, Inc.,
 20+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 21+ * http://www.gnu.org/copyleft/gpl.html
 22+ */
 23+
 24+class ApiGetGadgetPrefs extends ApiBase {
 25+
 26+ public function execute() {
 27+ $user = RequestContext::getMain()->getUser();
 28+
 29+ $params = $this->extractRequestParams();
 30+ //Check permissions
 31+ if ( !$user->isLoggedIn() ) {
 32+ $this->dieUsage( 'You must be logged-in to get gadget\'s preferences', 'notloggedin' );
 33+ }
 34+
 35+ $gadgetName = $params['gadget'];
 36+
 37+ $gadgets = Gadget::loadList();
 38+ $gadget = $gadgets && isset( $gadgets[$gadgetName] ) ? $gadgets[$gadgetName] : null;
 39+
 40+ if ( $gadget === null ) {
 41+ $this->dieUsage( 'Gadget not found', 'notfound' );
 42+ }
 43+
 44+ $prefsDescription = $gadget->getPrefsDescription();
 45+
 46+ if ( $prefsDescription === null ) {
 47+ $this->dieUsage( 'Gadget ' . $gadget->getName() . ' does not have any preference', 'noprefs' );
 48+ }
 49+
 50+ $userPrefs = $gadget->getPrefs();
 51+
 52+ if ( $userPrefs === null ) {
 53+ $this->dieUsage( 'An unexpected condition happened, please report this to the developers', 'unexpectederror' );
 54+ }
 55+
 56+ //Add user preferences to preference description
 57+ foreach ( $userPrefs as $pref => $value ) {
 58+ $prefsDescription['fields'][$pref]['value'] = $value;
 59+ }
 60+
 61+ $this->getResult()->addValue( null, $this->getModuleName(), $prefsDescription );
 62+ }
 63+
 64+ public function getAllowedParams() {
 65+ return array(
 66+ 'gadget' => array(
 67+ ApiBase::PARAM_TYPE => 'string',
 68+ ApiBase::PARAM_REQUIRED => true
 69+ )
 70+ );
 71+ }
 72+
 73+ public function getParamDescription() {
 74+ return array(
 75+ 'gadget' => 'The name of the gadget'
 76+ );
 77+ }
 78+
 79+ public function getDescription() {
 80+ return 'Allows user code to get preferences for gadgets, along with preference descriptions and values for currently logged in user';
 81+ }
 82+
 83+ public function getPossibleErrors() {
 84+ return array_merge( parent::getPossibleErrors(), array(
 85+ array( 'code' => 'notloggedin', 'info' => 'You must be logged-in to get gadget\'s preferences' ),
 86+ array( 'code' => 'notfound', 'info' => 'Gadget not found' ),
 87+ array( 'code' => 'noprefs', 'info' => 'Gadget gadgetname does not have any preferences' ),
 88+ ) );
 89+ }
 90+
 91+ public function getVersion() {
 92+ return __CLASS__ . ': $Id$';
 93+ }
 94+}
Property changes on: branches/salvatoreingala/Gadgets/api/ApiGetGadgetPrefs.php
___________________________________________________________________
Added: svn:keywords
195 + Id
Added: svn:eol-style
296 + native
Index: branches/salvatoreingala/Gadgets/ui/GadgetsMainModule.php
@@ -0,0 +1,39 @@
 2+<?php
 3+
 4+/**
 5+ * Gadgets extension - lets users select custom javascript gadgets
 6+ *
 7+ *
 8+ * For more info see http://mediawiki.org/wiki/Extension:Gadgets
 9+ *
 10+ * @file
 11+ * @ingroup Extensions
 12+ * @author Daniel Kinzler, brightbyte.de
 13+ * @copyright © 2007 Daniel Kinzler
 14+ * @license GNU General Public Licence 2.0 or later
 15+ */
 16+
 17+/**
 18+ * Class implementing the ext.gadgets module. Required by ext.gadgets.preferences.
 19+ */
 20+class GadgetsMainModule extends ResourceLoaderModule {
 21+ //TODO: should override getModifiedTime()
 22+
 23+ public function getScript( ResourceLoaderContext $context ) {
 24+ $configurableGadgets = array();
 25+ $gadgetsList = Gadget::loadStructuredList();
 26+
 27+ foreach ( $gadgetsList as $section => $gadgets ) {
 28+ foreach ( $gadgets as $gadgetName => $gadget ) {
 29+ $prefs = $gadget->getPrefsDescription();
 30+ if ( $prefs !== null ) {
 31+ $configurableGadgets[] = $gadget->getName();
 32+ }
 33+ }
 34+ }
 35+
 36+ $script = "mw.gadgets = {}\n";
 37+ $script .= "mw.gadgets.configurableGadgets = " . Xml::encodeJsVar( $configurableGadgets ) . ";\n";
 38+ return $script;
 39+ }
 40+}
Property changes on: branches/salvatoreingala/Gadgets/ui/GadgetsMainModule.php
___________________________________________________________________
Added: svn:eol-style
141 + native
Index: branches/salvatoreingala/Gadgets/ui/SpecialGadgets.php
@@ -0,0 +1,167 @@
 2+<?php
 3+/**
 4+ * Special:Gadgets, provides a preview of MediaWiki:Gadgets.
 5+ *
 6+ * @file
 7+ * @ingroup SpecialPage
 8+ * @author Daniel Kinzler, brightbyte.de
 9+ * @copyright © 2007 Daniel Kinzler
 10+ * @license GNU General Public License 2.0 or later
 11+ */
 12+
 13+if( !defined( 'MEDIAWIKI' ) ) {
 14+ echo( "not a valid entry point.\n" );
 15+ die( 1 );
 16+}
 17+
 18+/**
 19+ *
 20+ */
 21+class SpecialGadgets extends SpecialPage {
 22+
 23+ /**
 24+ * Constructor
 25+ */
 26+ function __construct() {
 27+ parent::__construct( 'Gadgets', '', true );
 28+ }
 29+
 30+ /**
 31+ * Main execution function
 32+ * @param $par Parameters passed to the page
 33+ */
 34+ function execute( $par ) {
 35+ $parts = explode( '/', $par );
 36+ if ( count( $parts ) == 2 && $parts[0] == 'export' ) {
 37+ $this->showExportForm( $parts[1] );
 38+ } else {
 39+ $this->showMainForm();
 40+ }
 41+ }
 42+
 43+ /**
 44+ * Displays form showing the list of installed gadgets
 45+ */
 46+ public function showMainForm() {
 47+ global $wgOut, $wgUser, $wgLang, $wgContLang;
 48+
 49+ $skin = $wgUser->getSkin();
 50+
 51+ $this->setHeaders();
 52+ $wgOut->setPagetitle( wfMsg( "gadgets-title" ) );
 53+ $wgOut->addWikiMsg( 'gadgets-pagetext' );
 54+
 55+ $gadgets = Gadget::loadStructuredList();
 56+ if ( !$gadgets ) return;
 57+
 58+ $lang = "";
 59+ if ( $wgLang->getCode() != $wgContLang->getCode() ) {
 60+ $lang = "/" . $wgLang->getCode();
 61+ }
 62+
 63+ $listOpen = false;
 64+
 65+ $msgOpt = array( 'parseinline', 'parsemag' );
 66+ $editInterfaceAllowed = $wgUser->isAllowed( 'editinterface' );
 67+
 68+ foreach ( $gadgets as $section => $entries ) {
 69+ if ( $section !== false && $section !== '' ) {
 70+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-section-$section$lang" );
 71+ if ( $editInterfaceAllowed ) {
 72+ $lnkTarget = $t
 73+ ? $skin->link( $t, wfMsgHTML( 'edit' ), array(), array( 'action' => 'edit' ) )
 74+ : htmlspecialchars( $section );
 75+ $lnk = "&#160; &#160; [$lnkTarget]";
 76+ } else {
 77+ $lnk = '';
 78+ }
 79+ $ttext = wfMsgExt( "gadget-section-$section", $msgOpt );
 80+
 81+ if( $listOpen ) {
 82+ $wgOut->addHTML( Xml::closeElement( 'ul' ) . "\n" );
 83+ $listOpen = false;
 84+ }
 85+ $wgOut->addHTML( Html::rawElement( 'h2', array(), $ttext . $lnk ) . "\n" );
 86+ }
 87+
 88+ foreach ( $entries as $gadget ) {
 89+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-{$gadget->getName()}$lang" );
 90+ if ( !$t ) continue;
 91+
 92+ $links = array();
 93+ if ( $editInterfaceAllowed ) {
 94+ $links[] = $skin->link( $t, wfMsgHTML( 'edit' ), array(), array( 'action' => 'edit' ) );
 95+ }
 96+ $links[] = $skin->link( $this->getTitle( "export/{$gadget->getName()}" ), wfMsgHtml( 'gadgets-export' ) );
 97+
 98+ $ttext = wfMsgExt( "gadget-{$gadget->getName()}", $msgOpt );
 99+
 100+ if( !$listOpen ) {
 101+ $listOpen = true;
 102+ $wgOut->addHTML( Xml::openElement( 'ul' ) );
 103+ }
 104+ $lnk = '&#160;&#160;' . wfMsg( 'parentheses', $wgLang->pipeList( $links ) );
 105+ $wgOut->addHTML( Xml::openElement( 'li' ) .
 106+ $ttext . $lnk . "<br />" .
 107+ wfMsgHTML( 'gadgets-uses' ) . wfMsg( 'colon-separator' )
 108+ );
 109+
 110+ $lnk = array();
 111+ foreach ( $gadget->getScriptsAndStyles() as $codePage ) {
 112+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, $codePage );
 113+ if ( !$t ) continue;
 114+
 115+ $lnk[] = $skin->link( $t, htmlspecialchars( $t->getText() ) );
 116+ }
 117+ $wgOut->addHTML( $wgLang->commaList( $lnk ) );
 118+ $rights = $gadget->getRequiredRights();
 119+ if ( count( $rights ) ) {
 120+ $wgOut->addHTML( '<br />' .
 121+ wfMessage( 'gadgets-required-rights', $wgLang->commaList( $rights ), count( $rights ) )->parse()
 122+ );
 123+ }
 124+ if ( $gadget->isOnByDefault() ) {
 125+ $wgOut->addHTML( '<br />' . wfMessage( 'gadgets-default' )->parse() );
 126+ }
 127+
 128+ $wgOut->addHTML( Xml::closeElement( 'li' ) . "\n" );
 129+ }
 130+ }
 131+
 132+ if( $listOpen ) {
 133+ $wgOut->addHTML( Xml::closeElement( 'ul' ) . "\n" );
 134+ }
 135+ }
 136+
 137+ /**
 138+ * Exports a gadget with its dependencies in a serialized form
 139+ * @param $gadget String Name of gadget to export
 140+ */
 141+ public function showExportForm( $gadget ) {
 142+ global $wgOut, $wgScript;
 143+
 144+ $gadgets = Gadget::loadList();
 145+ if ( !isset( $gadgets[$gadget] ) ) {
 146+ $wgOut->showErrorPage( 'error', 'gadgets-not-found', array( $gadget ) );
 147+ return;
 148+ }
 149+
 150+ $g = $gadgets[$gadget];
 151+ $this->setHeaders();
 152+ $wgOut->setPagetitle( wfMsg( "gadgets-export-title" ) );
 153+ $wgOut->addWikiMsg( 'gadgets-export-text', $gadget, $g->getDefinition() );
 154+
 155+ $exportList = "MediaWiki:gadget-$gadget\n";
 156+ foreach ( $g->getScriptsAndStyles() as $page ) {
 157+ $exportList .= "MediaWiki:$page\n";
 158+ }
 159+
 160+ $wgOut->addHTML( Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) )
 161+ . Html::hidden( 'title', SpecialPage::getTitleFor( 'Export' )->getPrefixedDBKey() )
 162+ . Html::hidden( 'pages', $exportList )
 163+ . Html::hidden( 'wpDownload', '1' )
 164+ . Xml::submitButton( wfMsg( 'gadgets-export-download' ) )
 165+ . Html::closeElement( 'form' )
 166+ );
 167+ }
 168+}
Property changes on: branches/salvatoreingala/Gadgets/ui/SpecialGadgets.php
___________________________________________________________________
Added: svn:eol-style
1169 + native
Index: branches/salvatoreingala/Gadgets/ui/resources/jquery.validate.js
@@ -0,0 +1,1166 @@
 2+/**
 3+ * jQuery Validation Plugin 1.8.1
 4+ *
 5+ * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
 6+ * http://docs.jquery.com/Plugins/Validation
 7+ *
 8+ * Copyright (c) 2006 - 2011 Jörn Zaefferer
 9+ *
 10+ * Dual licensed under the MIT and GPL licenses:
 11+ * http://www.opensource.org/licenses/mit-license.php
 12+ * http://www.gnu.org/licenses/gpl.html
 13+ */
 14+
 15+(function($) {
 16+
 17+$.extend($.fn, {
 18+ // http://docs.jquery.com/Plugins/Validation/validate
 19+ validate: function( options ) {
 20+
 21+ // if nothing is selected, return nothing; can't chain anyway
 22+ if (!this.length) {
 23+ options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" );
 24+ return;
 25+ }
 26+
 27+ // check if a validator for this form was already created
 28+ var validator = $.data(this[0], 'validator');
 29+ if ( validator ) {
 30+ return validator;
 31+ }
 32+
 33+ validator = new $.validator( options, this[0] );
 34+ $.data(this[0], 'validator', validator);
 35+
 36+ if ( validator.settings.onsubmit ) {
 37+
 38+ // allow suppresing validation by adding a cancel class to the submit button
 39+ this.find("input, button").filter(".cancel").click(function() {
 40+ validator.cancelSubmit = true;
 41+ });
 42+
 43+ // when a submitHandler is used, capture the submitting button
 44+ if (validator.settings.submitHandler) {
 45+ this.find("input, button").filter(":submit").click(function() {
 46+ validator.submitButton = this;
 47+ });
 48+ }
 49+
 50+ // validate the form on submit
 51+ this.submit( function( event ) {
 52+ if ( validator.settings.debug )
 53+ // prevent form submit to be able to see console output
 54+ event.preventDefault();
 55+
 56+ function handle() {
 57+ if ( validator.settings.submitHandler ) {
 58+ if (validator.submitButton) {
 59+ // insert a hidden input as a replacement for the missing submit button
 60+ var hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
 61+ }
 62+ validator.settings.submitHandler.call( validator, validator.currentForm );
 63+ if (validator.submitButton) {
 64+ // and clean up afterwards; thanks to no-block-scope, hidden can be referenced
 65+ hidden.remove();
 66+ }
 67+ return false;
 68+ }
 69+ return true;
 70+ }
 71+
 72+ // prevent submit for invalid forms or custom submit handlers
 73+ if ( validator.cancelSubmit ) {
 74+ validator.cancelSubmit = false;
 75+ return handle();
 76+ }
 77+ if ( validator.form() ) {
 78+ if ( validator.pendingRequest ) {
 79+ validator.formSubmitted = true;
 80+ return false;
 81+ }
 82+ return handle();
 83+ } else {
 84+ validator.focusInvalid();
 85+ return false;
 86+ }
 87+ });
 88+ }
 89+
 90+ return validator;
 91+ },
 92+ // http://docs.jquery.com/Plugins/Validation/valid
 93+ valid: function() {
 94+ if ( $(this[0]).is('form')) {
 95+ return this.validate().form();
 96+ } else {
 97+ var valid = true;
 98+ var validator = $(this[0].form).validate();
 99+ this.each(function() {
 100+ valid &= validator.element(this);
 101+ });
 102+ return valid;
 103+ }
 104+ },
 105+ // attributes: space seperated list of attributes to retrieve and remove
 106+ removeAttrs: function(attributes) {
 107+ var result = {},
 108+ $element = this;
 109+ $.each(attributes.split(/\s/), function(index, value) {
 110+ result[value] = $element.attr(value);
 111+ $element.removeAttr(value);
 112+ });
 113+ return result;
 114+ },
 115+ // http://docs.jquery.com/Plugins/Validation/rules
 116+ rules: function(command, argument) {
 117+ var element = this[0];
 118+
 119+ if (command) {
 120+ var settings = $.data(element.form, 'validator').settings;
 121+ var staticRules = settings.rules;
 122+ var existingRules = $.validator.staticRules(element);
 123+ switch(command) {
 124+ case "add":
 125+ $.extend(existingRules, $.validator.normalizeRule(argument));
 126+ staticRules[element.name] = existingRules;
 127+ if (argument.messages)
 128+ settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
 129+ break;
 130+ case "remove":
 131+ if (!argument) {
 132+ delete staticRules[element.name];
 133+ return existingRules;
 134+ }
 135+ var filtered = {};
 136+ $.each(argument.split(/\s/), function(index, method) {
 137+ filtered[method] = existingRules[method];
 138+ delete existingRules[method];
 139+ });
 140+ return filtered;
 141+ }
 142+ }
 143+
 144+ var data = $.validator.normalizeRules(
 145+ $.extend(
 146+ {},
 147+ $.validator.metadataRules(element),
 148+ $.validator.classRules(element),
 149+ $.validator.attributeRules(element),
 150+ $.validator.staticRules(element)
 151+ ), element);
 152+
 153+ // make sure required is at front
 154+ if (data.required) {
 155+ var param = data.required;
 156+ delete data.required;
 157+ data = $.extend({required: param}, data);
 158+ }
 159+
 160+ return data;
 161+ }
 162+});
 163+
 164+// Custom selectors
 165+$.extend($.expr[":"], {
 166+ // http://docs.jquery.com/Plugins/Validation/blank
 167+ blank: function(a) {return !$.trim("" + a.value);},
 168+ // http://docs.jquery.com/Plugins/Validation/filled
 169+ filled: function(a) {return !!$.trim("" + a.value);},
 170+ // http://docs.jquery.com/Plugins/Validation/unchecked
 171+ unchecked: function(a) {return !a.checked;}
 172+});
 173+
 174+// constructor for validator
 175+$.validator = function( options, form ) {
 176+ this.settings = $.extend( true, {}, $.validator.defaults, options );
 177+ this.currentForm = form;
 178+ this.init();
 179+};
 180+
 181+$.validator.format = function(source, params) {
 182+ if ( arguments.length == 1 )
 183+ return function() {
 184+ var args = $.makeArray(arguments);
 185+ args.unshift(source);
 186+ return $.validator.format.apply( this, args );
 187+ };
 188+ if ( arguments.length > 2 && params.constructor != Array ) {
 189+ params = $.makeArray(arguments).slice(1);
 190+ }
 191+ if ( params.constructor != Array ) {
 192+ params = [ params ];
 193+ }
 194+ $.each(params, function(i, n) {
 195+ source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
 196+ });
 197+ return source;
 198+};
 199+
 200+$.extend($.validator, {
 201+
 202+ defaults: {
 203+ messages: {},
 204+ groups: {},
 205+ rules: {},
 206+ errorClass: "error",
 207+ validClass: "valid",
 208+ errorElement: "label",
 209+ focusInvalid: true,
 210+ errorContainer: $( [] ),
 211+ errorLabelContainer: $( [] ),
 212+ onsubmit: true,
 213+ ignore: [],
 214+ ignoreTitle: false,
 215+ onfocusin: function(element) {
 216+ this.lastActive = element;
 217+
 218+ // hide error label and remove error class on focus if enabled
 219+ if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
 220+ this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
 221+ this.addWrapper(this.errorsFor(element)).hide();
 222+ }
 223+ },
 224+ onfocusout: function(element) {
 225+ if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
 226+ this.element(element);
 227+ }
 228+ },
 229+ onkeyup: function(element) {
 230+ if ( element.name in this.submitted || element == this.lastElement ) {
 231+ this.element(element);
 232+ }
 233+ },
 234+ onclick: function(element) {
 235+ // click on selects, radiobuttons and checkboxes
 236+ if ( element.name in this.submitted )
 237+ this.element(element);
 238+ // or option elements, check parent select in that case
 239+ else if (element.parentNode.name in this.submitted)
 240+ this.element(element.parentNode);
 241+ },
 242+ highlight: function(element, errorClass, validClass) {
 243+ if (element.type === 'radio') {
 244+ this.findByName(element.name).addClass(errorClass).removeClass(validClass);
 245+ } else {
 246+ $(element).addClass(errorClass).removeClass(validClass);
 247+ }
 248+ },
 249+ unhighlight: function(element, errorClass, validClass) {
 250+ if (element.type === 'radio') {
 251+ this.findByName(element.name).removeClass(errorClass).addClass(validClass);
 252+ } else {
 253+ $(element).removeClass(errorClass).addClass(validClass);
 254+ }
 255+ }
 256+ },
 257+
 258+ // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
 259+ setDefaults: function(settings) {
 260+ $.extend( $.validator.defaults, settings );
 261+ },
 262+
 263+ messages: {
 264+ required: "This field is required.",
 265+ remote: "Please fix this field.",
 266+ email: "Please enter a valid email address.",
 267+ url: "Please enter a valid URL.",
 268+ date: "Please enter a valid date.",
 269+ dateISO: "Please enter a valid date (ISO).",
 270+ number: "Please enter a valid number.",
 271+ digits: "Please enter only digits.",
 272+ creditcard: "Please enter a valid credit card number.",
 273+ equalTo: "Please enter the same value again.",
 274+ accept: "Please enter a value with a valid extension.",
 275+ maxlength: $.validator.format("Please enter no more than {0} characters."),
 276+ minlength: $.validator.format("Please enter at least {0} characters."),
 277+ rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
 278+ range: $.validator.format("Please enter a value between {0} and {1}."),
 279+ max: $.validator.format("Please enter a value less than or equal to {0}."),
 280+ min: $.validator.format("Please enter a value greater than or equal to {0}.")
 281+ },
 282+
 283+ autoCreateRanges: false,
 284+
 285+ prototype: {
 286+
 287+ init: function() {
 288+ this.labelContainer = $(this.settings.errorLabelContainer);
 289+ this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
 290+ this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
 291+ this.submitted = {};
 292+ this.valueCache = {};
 293+ this.pendingRequest = 0;
 294+ this.pending = {};
 295+ this.invalid = {};
 296+ this.reset();
 297+
 298+ var groups = (this.groups = {});
 299+ $.each(this.settings.groups, function(key, value) {
 300+ $.each(value.split(/\s/), function(index, name) {
 301+ groups[name] = key;
 302+ });
 303+ });
 304+ var rules = this.settings.rules;
 305+ $.each(rules, function(key, value) {
 306+ rules[key] = $.validator.normalizeRule(value);
 307+ });
 308+
 309+ function delegate(event) {
 310+ var validator = $.data(this[0].form, "validator"),
 311+ eventType = "on" + event.type.replace(/^validate/, "");
 312+ validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] );
 313+ }
 314+ $(this.currentForm)
 315+ .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate)
 316+ .validateDelegate(":radio, :checkbox, select, option", "click", delegate);
 317+
 318+ if (this.settings.invalidHandler)
 319+ $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
 320+ },
 321+
 322+ // http://docs.jquery.com/Plugins/Validation/Validator/form
 323+ form: function() {
 324+ this.checkForm();
 325+ $.extend(this.submitted, this.errorMap);
 326+ this.invalid = $.extend({}, this.errorMap);
 327+ if (!this.valid())
 328+ $(this.currentForm).triggerHandler("invalid-form", [this]);
 329+ this.showErrors();
 330+ return this.valid();
 331+ },
 332+
 333+ checkForm: function() {
 334+ this.prepareForm();
 335+ for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
 336+ this.check( elements[i] );
 337+ }
 338+ return this.valid();
 339+ },
 340+
 341+ // http://docs.jquery.com/Plugins/Validation/Validator/element
 342+ element: function( element ) {
 343+ element = this.clean( element );
 344+ this.lastElement = element;
 345+ this.prepareElement( element );
 346+ this.currentElements = $(element);
 347+ var result = this.check( element );
 348+ if ( result ) {
 349+ delete this.invalid[element.name];
 350+ } else {
 351+ this.invalid[element.name] = true;
 352+ }
 353+ if ( !this.numberOfInvalids() ) {
 354+ // Hide error containers on last error
 355+ this.toHide = this.toHide.add( this.containers );
 356+ }
 357+ this.showErrors();
 358+ return result;
 359+ },
 360+
 361+ // http://docs.jquery.com/Plugins/Validation/Validator/showErrors
 362+ showErrors: function(errors) {
 363+ if(errors) {
 364+ // add items to error list and map
 365+ $.extend( this.errorMap, errors );
 366+ this.errorList = [];
 367+ for ( var name in errors ) {
 368+ this.errorList.push({
 369+ message: errors[name],
 370+ element: this.findByName(name)[0]
 371+ });
 372+ }
 373+ // remove items from success list
 374+ this.successList = $.grep( this.successList, function(element) {
 375+ return !(element.name in errors);
 376+ });
 377+ }
 378+ this.settings.showErrors
 379+ ? this.settings.showErrors.call( this, this.errorMap, this.errorList )
 380+ : this.defaultShowErrors();
 381+ },
 382+
 383+ // http://docs.jquery.com/Plugins/Validation/Validator/resetForm
 384+ resetForm: function() {
 385+ if ( $.fn.resetForm )
 386+ $( this.currentForm ).resetForm();
 387+ this.submitted = {};
 388+ this.prepareForm();
 389+ this.hideErrors();
 390+ this.elements().removeClass( this.settings.errorClass );
 391+ },
 392+
 393+ numberOfInvalids: function() {
 394+ return this.objectLength(this.invalid);
 395+ },
 396+
 397+ objectLength: function( obj ) {
 398+ var count = 0;
 399+ for ( var i in obj )
 400+ count++;
 401+ return count;
 402+ },
 403+
 404+ hideErrors: function() {
 405+ this.addWrapper( this.toHide ).hide();
 406+ },
 407+
 408+ valid: function() {
 409+ return this.size() == 0;
 410+ },
 411+
 412+ size: function() {
 413+ return this.errorList.length;
 414+ },
 415+
 416+ focusInvalid: function() {
 417+ if( this.settings.focusInvalid ) {
 418+ try {
 419+ $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
 420+ .filter(":visible")
 421+ .focus()
 422+ // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
 423+ .trigger("focusin");
 424+ } catch(e) {
 425+ // ignore IE throwing errors when focusing hidden elements
 426+ }
 427+ }
 428+ },
 429+
 430+ findLastActive: function() {
 431+ var lastActive = this.lastActive;
 432+ return lastActive && $.grep(this.errorList, function(n) {
 433+ return n.element.name == lastActive.name;
 434+ }).length == 1 && lastActive;
 435+ },
 436+
 437+ elements: function() {
 438+ var validator = this,
 439+ rulesCache = {};
 440+
 441+ // select all valid inputs inside the form (no submit or reset buttons)
 442+ return $(this.currentForm)
 443+ .find("input, select, textarea")
 444+ .not(":submit, :reset, :image, [disabled]")
 445+ .not( this.settings.ignore )
 446+ .filter(function() {
 447+ !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
 448+
 449+ // select only the first element for each name, and only those with rules specified
 450+ if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
 451+ return false;
 452+
 453+ rulesCache[this.name] = true;
 454+ return true;
 455+ });
 456+ },
 457+
 458+ clean: function( selector ) {
 459+ return $( selector )[0];
 460+ },
 461+
 462+ errors: function() {
 463+ return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext );
 464+ },
 465+
 466+ reset: function() {
 467+ this.successList = [];
 468+ this.errorList = [];
 469+ this.errorMap = {};
 470+ this.toShow = $([]);
 471+ this.toHide = $([]);
 472+ this.currentElements = $([]);
 473+ },
 474+
 475+ prepareForm: function() {
 476+ this.reset();
 477+ this.toHide = this.errors().add( this.containers );
 478+ },
 479+
 480+ prepareElement: function( element ) {
 481+ this.reset();
 482+ this.toHide = this.errorsFor(element);
 483+ },
 484+
 485+ check: function( element ) {
 486+ element = this.clean( element );
 487+
 488+ // if radio/checkbox, validate first element in group instead
 489+ if (this.checkable(element)) {
 490+ element = this.findByName( element.name ).not(this.settings.ignore)[0];
 491+ }
 492+
 493+ var rules = $(element).rules();
 494+ var dependencyMismatch = false;
 495+ for (var method in rules ) {
 496+ var rule = { method: method, parameters: rules[method] };
 497+ try {
 498+ var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
 499+
 500+ // if a method indicates that the field is optional and therefore valid,
 501+ // don't mark it as valid when there are no other rules
 502+ if ( result == "dependency-mismatch" ) {
 503+ dependencyMismatch = true;
 504+ continue;
 505+ }
 506+ dependencyMismatch = false;
 507+
 508+ if ( result == "pending" ) {
 509+ this.toHide = this.toHide.not( this.errorsFor(element) );
 510+ return;
 511+ }
 512+
 513+ if( !result ) {
 514+ this.formatAndAdd( element, rule );
 515+ return false;
 516+ }
 517+ } catch(e) {
 518+ this.settings.debug && window.console && console.log("exception occured when checking element " + element.id
 519+ + ", check the '" + rule.method + "' method", e);
 520+ throw e;
 521+ }
 522+ }
 523+ if (dependencyMismatch)
 524+ return;
 525+ if ( this.objectLength(rules) )
 526+ this.successList.push(element);
 527+ return true;
 528+ },
 529+
 530+ // return the custom message for the given element and validation method
 531+ // specified in the element's "messages" metadata
 532+ customMetaMessage: function(element, method) {
 533+ if (!$.metadata)
 534+ return;
 535+
 536+ var meta = this.settings.meta
 537+ ? $(element).metadata()[this.settings.meta]
 538+ : $(element).metadata();
 539+
 540+ return meta && meta.messages && meta.messages[method];
 541+ },
 542+
 543+ // return the custom message for the given element name and validation method
 544+ customMessage: function( name, method ) {
 545+ var m = this.settings.messages[name];
 546+ return m && (m.constructor == String
 547+ ? m
 548+ : m[method]);
 549+ },
 550+
 551+ // return the first defined argument, allowing empty strings
 552+ findDefined: function() {
 553+ for(var i = 0; i < arguments.length; i++) {
 554+ if (arguments[i] !== undefined)
 555+ return arguments[i];
 556+ }
 557+ return undefined;
 558+ },
 559+
 560+ defaultMessage: function( element, method) {
 561+ return this.findDefined(
 562+ this.customMessage( element.name, method ),
 563+ this.customMetaMessage( element, method ),
 564+ // title is never undefined, so handle empty string as undefined
 565+ !this.settings.ignoreTitle && element.title || undefined,
 566+ $.validator.messages[method],
 567+ "<strong>Warning: No message defined for " + element.name + "</strong>"
 568+ );
 569+ },
 570+
 571+ formatAndAdd: function( element, rule ) {
 572+ var message = this.defaultMessage( element, rule.method ),
 573+ theregex = /\$?\{(\d+)\}/g;
 574+ if ( typeof message == "function" ) {
 575+ message = message.call(this, rule.parameters, element);
 576+ } else if (theregex.test(message)) {
 577+ message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters);
 578+ }
 579+ this.errorList.push({
 580+ message: message,
 581+ element: element
 582+ });
 583+
 584+ this.errorMap[element.name] = message;
 585+ this.submitted[element.name] = message;
 586+ },
 587+
 588+ addWrapper: function(toToggle) {
 589+ if ( this.settings.wrapper )
 590+ toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
 591+ return toToggle;
 592+ },
 593+
 594+ defaultShowErrors: function() {
 595+ for ( var i = 0; this.errorList[i]; i++ ) {
 596+ var error = this.errorList[i];
 597+ this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
 598+ this.showLabel( error.element, error.message );
 599+ }
 600+ if( this.errorList.length ) {
 601+ this.toShow = this.toShow.add( this.containers );
 602+ }
 603+ if (this.settings.success) {
 604+ for ( var i = 0; this.successList[i]; i++ ) {
 605+ this.showLabel( this.successList[i] );
 606+ }
 607+ }
 608+ if (this.settings.unhighlight) {
 609+ for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) {
 610+ this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
 611+ }
 612+ }
 613+ this.toHide = this.toHide.not( this.toShow );
 614+ this.hideErrors();
 615+ this.addWrapper( this.toShow ).show();
 616+ },
 617+
 618+ validElements: function() {
 619+ return this.currentElements.not(this.invalidElements());
 620+ },
 621+
 622+ invalidElements: function() {
 623+ return $(this.errorList).map(function() {
 624+ return this.element;
 625+ });
 626+ },
 627+
 628+ showLabel: function(element, message) {
 629+ var label = this.errorsFor( element );
 630+ if ( label.length ) {
 631+ // refresh error/success class
 632+ label.removeClass().addClass( this.settings.errorClass );
 633+
 634+ // check if we have a generated label, replace the message then
 635+ label.attr("generated") && label.html(message);
 636+ } else {
 637+ // create label
 638+ label = $("<" + this.settings.errorElement + "/>")
 639+ .attr({"for": this.idOrName(element), generated: true})
 640+ .addClass(this.settings.errorClass)
 641+ .html(message || "");
 642+ if ( this.settings.wrapper ) {
 643+ // make sure the element is visible, even in IE
 644+ // actually showing the wrapped element is handled elsewhere
 645+ label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
 646+ }
 647+ if ( !this.labelContainer.append(label).length )
 648+ this.settings.errorPlacement
 649+ ? this.settings.errorPlacement(label, $(element) )
 650+ : label.insertAfter(element);
 651+ }
 652+ if ( !message && this.settings.success ) {
 653+ label.text("");
 654+ typeof this.settings.success == "string"
 655+ ? label.addClass( this.settings.success )
 656+ : this.settings.success( label );
 657+ }
 658+ this.toShow = this.toShow.add(label);
 659+ },
 660+
 661+ errorsFor: function(element) {
 662+ var name = this.idOrName(element);
 663+ return this.errors().filter(function() {
 664+ return $(this).attr('for') == name;
 665+ });
 666+ },
 667+
 668+ idOrName: function(element) {
 669+ return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
 670+ },
 671+
 672+ checkable: function( element ) {
 673+ return /radio|checkbox/i.test(element.type);
 674+ },
 675+
 676+ findByName: function( name ) {
 677+ // select by name and filter by form for performance over form.find("[name=...]")
 678+ var form = this.currentForm;
 679+ return $(document.getElementsByName(name)).map(function(index, element) {
 680+ return element.form == form && element.name == name && element || null;
 681+ });
 682+ },
 683+
 684+ getLength: function(value, element) {
 685+ switch( element.nodeName.toLowerCase() ) {
 686+ case 'select':
 687+ return $("option:selected", element).length;
 688+ case 'input':
 689+ if( this.checkable( element) )
 690+ return this.findByName(element.name).filter(':checked').length;
 691+ }
 692+ return value.length;
 693+ },
 694+
 695+ depend: function(param, element) {
 696+ return this.dependTypes[typeof param]
 697+ ? this.dependTypes[typeof param](param, element)
 698+ : true;
 699+ },
 700+
 701+ dependTypes: {
 702+ "boolean": function(param, element) {
 703+ return param;
 704+ },
 705+ "string": function(param, element) {
 706+ return !!$(param, element.form).length;
 707+ },
 708+ "function": function(param, element) {
 709+ return param(element);
 710+ }
 711+ },
 712+
 713+ optional: function(element) {
 714+ return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch";
 715+ },
 716+
 717+ startRequest: function(element) {
 718+ if (!this.pending[element.name]) {
 719+ this.pendingRequest++;
 720+ this.pending[element.name] = true;
 721+ }
 722+ },
 723+
 724+ stopRequest: function(element, valid) {
 725+ this.pendingRequest--;
 726+ // sometimes synchronization fails, make sure pendingRequest is never < 0
 727+ if (this.pendingRequest < 0)
 728+ this.pendingRequest = 0;
 729+ delete this.pending[element.name];
 730+ if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) {
 731+ $(this.currentForm).submit();
 732+ this.formSubmitted = false;
 733+ } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) {
 734+ $(this.currentForm).triggerHandler("invalid-form", [this]);
 735+ this.formSubmitted = false;
 736+ }
 737+ },
 738+
 739+ previousValue: function(element) {
 740+ return $.data(element, "previousValue") || $.data(element, "previousValue", {
 741+ old: null,
 742+ valid: true,
 743+ message: this.defaultMessage( element, "remote" )
 744+ });
 745+ }
 746+
 747+ },
 748+
 749+ classRuleSettings: {
 750+ required: {required: true},
 751+ email: {email: true},
 752+ url: {url: true},
 753+ date: {date: true},
 754+ dateISO: {dateISO: true},
 755+ dateDE: {dateDE: true},
 756+ number: {number: true},
 757+ numberDE: {numberDE: true},
 758+ digits: {digits: true},
 759+ creditcard: {creditcard: true}
 760+ },
 761+
 762+ addClassRules: function(className, rules) {
 763+ className.constructor == String ?
 764+ this.classRuleSettings[className] = rules :
 765+ $.extend(this.classRuleSettings, className);
 766+ },
 767+
 768+ classRules: function(element) {
 769+ var rules = {};
 770+ var classes = $(element).attr('class');
 771+ classes && $.each(classes.split(' '), function() {
 772+ if (this in $.validator.classRuleSettings) {
 773+ $.extend(rules, $.validator.classRuleSettings[this]);
 774+ }
 775+ });
 776+ return rules;
 777+ },
 778+
 779+ attributeRules: function(element) {
 780+ var rules = {};
 781+ var $element = $(element);
 782+
 783+ for (var method in $.validator.methods) {
 784+ var value = $element.attr(method);
 785+ if (value) {
 786+ rules[method] = value;
 787+ }
 788+ }
 789+
 790+ // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
 791+ if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
 792+ delete rules.maxlength;
 793+ }
 794+
 795+ return rules;
 796+ },
 797+
 798+ metadataRules: function(element) {
 799+ if (!$.metadata) return {};
 800+
 801+ var meta = $.data(element.form, 'validator').settings.meta;
 802+ return meta ?
 803+ $(element).metadata()[meta] :
 804+ $(element).metadata();
 805+ },
 806+
 807+ staticRules: function(element) {
 808+ var rules = {};
 809+ var validator = $.data(element.form, 'validator');
 810+ if (validator.settings.rules) {
 811+ rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
 812+ }
 813+ return rules;
 814+ },
 815+
 816+ normalizeRules: function(rules, element) {
 817+ // handle dependency check
 818+ $.each(rules, function(prop, val) {
 819+ // ignore rule when param is explicitly false, eg. required:false
 820+ if (val === false) {
 821+ delete rules[prop];
 822+ return;
 823+ }
 824+ if (val.param || val.depends) {
 825+ var keepRule = true;
 826+ switch (typeof val.depends) {
 827+ case "string":
 828+ keepRule = !!$(val.depends, element.form).length;
 829+ break;
 830+ case "function":
 831+ keepRule = val.depends.call(element, element);
 832+ break;
 833+ }
 834+ if (keepRule) {
 835+ rules[prop] = val.param !== undefined ? val.param : true;
 836+ } else {
 837+ delete rules[prop];
 838+ }
 839+ }
 840+ });
 841+
 842+ // evaluate parameters
 843+ $.each(rules, function(rule, parameter) {
 844+ rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
 845+ });
 846+
 847+ // clean number parameters
 848+ $.each(['minlength', 'maxlength', 'min', 'max'], function() {
 849+ if (rules[this]) {
 850+ rules[this] = Number(rules[this]);
 851+ }
 852+ });
 853+ $.each(['rangelength', 'range'], function() {
 854+ if (rules[this]) {
 855+ rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
 856+ }
 857+ });
 858+
 859+ if ($.validator.autoCreateRanges) {
 860+ // auto-create ranges
 861+ if (rules.min && rules.max) {
 862+ rules.range = [rules.min, rules.max];
 863+ delete rules.min;
 864+ delete rules.max;
 865+ }
 866+ if (rules.minlength && rules.maxlength) {
 867+ rules.rangelength = [rules.minlength, rules.maxlength];
 868+ delete rules.minlength;
 869+ delete rules.maxlength;
 870+ }
 871+ }
 872+
 873+ // To support custom messages in metadata ignore rule methods titled "messages"
 874+ if (rules.messages) {
 875+ delete rules.messages;
 876+ }
 877+
 878+ return rules;
 879+ },
 880+
 881+ // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
 882+ normalizeRule: function(data) {
 883+ if( typeof data == "string" ) {
 884+ var transformed = {};
 885+ $.each(data.split(/\s/), function() {
 886+ transformed[this] = true;
 887+ });
 888+ data = transformed;
 889+ }
 890+ return data;
 891+ },
 892+
 893+ // http://docs.jquery.com/Plugins/Validation/Validator/addMethod
 894+ addMethod: function(name, method, message) {
 895+ $.validator.methods[name] = method;
 896+ $.validator.messages[name] = message != undefined ? message : $.validator.messages[name];
 897+ if (method.length < 3) {
 898+ $.validator.addClassRules(name, $.validator.normalizeRule(name));
 899+ }
 900+ },
 901+
 902+ methods: {
 903+
 904+ // http://docs.jquery.com/Plugins/Validation/Methods/required
 905+ required: function(value, element, param) {
 906+ // check if dependency is met
 907+ if ( !this.depend(param, element) )
 908+ return "dependency-mismatch";
 909+ switch( element.nodeName.toLowerCase() ) {
 910+ case 'select':
 911+ // could be an array for select-multiple or a string, both are fine this way
 912+ var val = $(element).val();
 913+ return val && val.length > 0;
 914+ case 'input':
 915+ if ( this.checkable(element) )
 916+ return this.getLength(value, element) > 0;
 917+ default:
 918+ return $.trim(value).length > 0;
 919+ }
 920+ },
 921+
 922+ // http://docs.jquery.com/Plugins/Validation/Methods/remote
 923+ remote: function(value, element, param) {
 924+ if ( this.optional(element) )
 925+ return "dependency-mismatch";
 926+
 927+ var previous = this.previousValue(element);
 928+ if (!this.settings.messages[element.name] )
 929+ this.settings.messages[element.name] = {};
 930+ previous.originalMessage = this.settings.messages[element.name].remote;
 931+ this.settings.messages[element.name].remote = previous.message;
 932+
 933+ param = typeof param == "string" && {url:param} || param;
 934+
 935+ if ( this.pending[element.name] ) {
 936+ return "pending";
 937+ }
 938+ if ( previous.old === value ) {
 939+ return previous.valid;
 940+ }
 941+
 942+ previous.old = value;
 943+ var validator = this;
 944+ this.startRequest(element);
 945+ var data = {};
 946+ data[element.name] = value;
 947+ $.ajax($.extend(true, {
 948+ url: param,
 949+ mode: "abort",
 950+ port: "validate" + element.name,
 951+ dataType: "json",
 952+ data: data,
 953+ success: function(response) {
 954+ validator.settings.messages[element.name].remote = previous.originalMessage;
 955+ var valid = response === true;
 956+ if ( valid ) {
 957+ var submitted = validator.formSubmitted;
 958+ validator.prepareElement(element);
 959+ validator.formSubmitted = submitted;
 960+ validator.successList.push(element);
 961+ validator.showErrors();
 962+ } else {
 963+ var errors = {};
 964+ var message = response || validator.defaultMessage( element, "remote" );
 965+ errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
 966+ validator.showErrors(errors);
 967+ }
 968+ previous.valid = valid;
 969+ validator.stopRequest(element, valid);
 970+ }
 971+ }, param));
 972+ return "pending";
 973+ },
 974+
 975+ // http://docs.jquery.com/Plugins/Validation/Methods/minlength
 976+ minlength: function(value, element, param) {
 977+ return this.optional(element) || this.getLength($.trim(value), element) >= param;
 978+ },
 979+
 980+ // http://docs.jquery.com/Plugins/Validation/Methods/maxlength
 981+ maxlength: function(value, element, param) {
 982+ return this.optional(element) || this.getLength($.trim(value), element) <= param;
 983+ },
 984+
 985+ // http://docs.jquery.com/Plugins/Validation/Methods/rangelength
 986+ rangelength: function(value, element, param) {
 987+ var length = this.getLength($.trim(value), element);
 988+ return this.optional(element) || ( length >= param[0] && length <= param[1] );
 989+ },
 990+
 991+ // http://docs.jquery.com/Plugins/Validation/Methods/min
 992+ min: function( value, element, param ) {
 993+ return this.optional(element) || value >= param;
 994+ },
 995+
 996+ // http://docs.jquery.com/Plugins/Validation/Methods/max
 997+ max: function( value, element, param ) {
 998+ return this.optional(element) || value <= param;
 999+ },
 1000+
 1001+ // http://docs.jquery.com/Plugins/Validation/Methods/range
 1002+ range: function( value, element, param ) {
 1003+ return this.optional(element) || ( value >= param[0] && value <= param[1] );
 1004+ },
 1005+
 1006+ // http://docs.jquery.com/Plugins/Validation/Methods/email
 1007+ email: function(value, element) {
 1008+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
 1009+ return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
 1010+ },
 1011+
 1012+ // http://docs.jquery.com/Plugins/Validation/Methods/url
 1013+ url: function(value, element) {
 1014+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
 1015+ return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
 1016+ },
 1017+
 1018+ // http://docs.jquery.com/Plugins/Validation/Methods/date
 1019+ date: function(value, element) {
 1020+ return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
 1021+ },
 1022+
 1023+ // http://docs.jquery.com/Plugins/Validation/Methods/dateISO
 1024+ dateISO: function(value, element) {
 1025+ return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value);
 1026+ },
 1027+
 1028+ // http://docs.jquery.com/Plugins/Validation/Methods/number
 1029+ number: function(value, element) {
 1030+ return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
 1031+ },
 1032+
 1033+ // http://docs.jquery.com/Plugins/Validation/Methods/digits
 1034+ digits: function(value, element) {
 1035+ return this.optional(element) || /^\d+$/.test(value);
 1036+ },
 1037+
 1038+ // http://docs.jquery.com/Plugins/Validation/Methods/creditcard
 1039+ // based on http://en.wikipedia.org/wiki/Luhn
 1040+ creditcard: function(value, element) {
 1041+ if ( this.optional(element) )
 1042+ return "dependency-mismatch";
 1043+ // accept only digits and dashes
 1044+ if (/[^0-9-]+/.test(value))
 1045+ return false;
 1046+ var nCheck = 0,
 1047+ nDigit = 0,
 1048+ bEven = false;
 1049+
 1050+ value = value.replace(/\D/g, "");
 1051+
 1052+ for (var n = value.length - 1; n >= 0; n--) {
 1053+ var cDigit = value.charAt(n);
 1054+ var nDigit = parseInt(cDigit, 10);
 1055+ if (bEven) {
 1056+ if ((nDigit *= 2) > 9)
 1057+ nDigit -= 9;
 1058+ }
 1059+ nCheck += nDigit;
 1060+ bEven = !bEven;
 1061+ }
 1062+
 1063+ return (nCheck % 10) == 0;
 1064+ },
 1065+
 1066+ // http://docs.jquery.com/Plugins/Validation/Methods/accept
 1067+ accept: function(value, element, param) {
 1068+ param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
 1069+ return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
 1070+ },
 1071+
 1072+ // http://docs.jquery.com/Plugins/Validation/Methods/equalTo
 1073+ equalTo: function(value, element, param) {
 1074+ // bind to the blur event of the target in order to revalidate whenever the target field is updated
 1075+ // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
 1076+ var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
 1077+ $(element).valid();
 1078+ });
 1079+ return value == target.val();
 1080+ }
 1081+
 1082+ }
 1083+
 1084+});
 1085+
 1086+// deprecated, use $.validator.format instead
 1087+$.format = $.validator.format;
 1088+
 1089+})(jQuery);
 1090+
 1091+// ajax mode: abort
 1092+// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
 1093+// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
 1094+;(function($) {
 1095+ var pendingRequests = {};
 1096+ // Use a prefilter if available (1.5+)
 1097+ if ( $.ajaxPrefilter ) {
 1098+ $.ajaxPrefilter(function(settings, _, xhr) {
 1099+ var port = settings.port;
 1100+ if (settings.mode == "abort") {
 1101+ if ( pendingRequests[port] ) {
 1102+ pendingRequests[port].abort();
 1103+ }
 1104+ pendingRequests[port] = xhr;
 1105+ }
 1106+ });
 1107+ } else {
 1108+ // Proxy ajax
 1109+ var ajax = $.ajax;
 1110+ $.ajax = function(settings) {
 1111+ var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode,
 1112+ port = ( "port" in settings ? settings : $.ajaxSettings ).port;
 1113+ if (mode == "abort") {
 1114+ if ( pendingRequests[port] ) {
 1115+ pendingRequests[port].abort();
 1116+ }
 1117+ return (pendingRequests[port] = ajax.apply(this, arguments));
 1118+ }
 1119+ return ajax.apply(this, arguments);
 1120+ };
 1121+ }
 1122+})(jQuery);
 1123+
 1124+// provides cross-browser focusin and focusout events
 1125+// IE has native support, in other browsers, use event caputuring (neither bubbles)
 1126+
 1127+// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
 1128+// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
 1129+;(function($) {
 1130+ // only implement if not provided by jQuery core (since 1.4)
 1131+ // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs
 1132+ if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) {
 1133+ $.each({
 1134+ focus: 'focusin',
 1135+ blur: 'focusout'
 1136+ }, function( original, fix ){
 1137+ $.event.special[fix] = {
 1138+ setup:function() {
 1139+ this.addEventListener( original, handler, true );
 1140+ },
 1141+ teardown:function() {
 1142+ this.removeEventListener( original, handler, true );
 1143+ },
 1144+ handler: function(e) {
 1145+ arguments[0] = $.event.fix(e);
 1146+ arguments[0].type = fix;
 1147+ return $.event.handle.apply(this, arguments);
 1148+ }
 1149+ };
 1150+ function handler(e) {
 1151+ e = $.event.fix(e);
 1152+ e.type = fix;
 1153+ return $.event.handle.call(this, e);
 1154+ }
 1155+ });
 1156+ };
 1157+ $.extend($.fn, {
 1158+ validateDelegate: function(delegate, type, handler) {
 1159+ return this.bind(type, function(event) {
 1160+ var target = $(event.target);
 1161+ if (target.is(delegate)) {
 1162+ return handler.apply(target, arguments);
 1163+ }
 1164+ });
 1165+ }
 1166+ });
 1167+})(jQuery);
Property changes on: branches/salvatoreingala/Gadgets/ui/resources/jquery.validate.js
___________________________________________________________________
Added: svn:eol-style
11168 + native
Index: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js
@@ -0,0 +1,415 @@
 2+/**
 3+ * jQuery Form Builder
 4+ * Written by Salvatore Ingala in 2011
 5+ * Released under the MIT and GPL licenses.
 6+ */
 7+
 8+(function($, mw) {
 9+
 10+ var idPrefix = "mw-gadgets-dialog-";
 11+
 12+ //Preprocesses strings end possibly replaces them with messages.
 13+ //If str starts with "@" the rest of the string is assumed to be
 14+ //a message, and the result of mw.msg is returned.
 15+ //Two "@@" at the beginning escape for a single "@".
 16+ function preproc( str ) {
 17+ if ( str.length <= 1 || str[0] !== '@' ) {
 18+ return str;
 19+ } else if ( str.substr( 0, 2 ) == '@@' ) {
 20+ return str.substr( 1 );
 21+ } else {
 22+ //TODO: better validation
 23+ return mw.msg( str.substring( 1 ) );
 24+ }
 25+ }
 26+
 27+
 28+ function testOptional( value, element ) {
 29+ var rules = $( element ).rules();
 30+ if ( typeof rules.required == 'undefined' || rules.required === false ) {
 31+ if ( value.length == 0 ) {
 32+ return true;
 33+ }
 34+ }
 35+ return false;
 36+ }
 37+
 38+ //validator for "required" fields (without trimming whitespaces)
 39+ $.validator.addMethod( "requiredStrict", function( value, element ) {
 40+ return value.length > 0;
 41+ }, mw.msg( 'gadgets-formbuilder-required' ) );
 42+
 43+ //validator for "minlength" fields (without trimming whitespaces)
 44+ $.validator.addMethod( "minlengthStrict", function( value, element, param ) {
 45+ return testOptional( value, element ) || value.length >= param;
 46+ } );
 47+
 48+ //validator for "maxlength" fields (without trimming whitespaces)
 49+ $.validator.addMethod( "maxlengthStrict", function( value, element, param ) {
 50+ return testOptional( value, element ) || value.length <= param;
 51+ } );
 52+
 53+ //validator for integer fields
 54+ $.validator.addMethod( "integer", function( value, element ) {
 55+ return testOptional( value, element ) || /^-?\d+$/.test(value);
 56+ }, mw.msg( 'gadgets-formbuilder-integer' ) );
 57+
 58+
 59+ //Helper function for inheritance, see http://javascript.crockford.com/prototypal.html
 60+ function object(o) {
 61+ function F() {}
 62+ F.prototype = o;
 63+ return new F();
 64+ }
 65+
 66+ //A field with no content
 67+ function EmptyField( name, desc ) {
 68+ //Check existence of compulsory fields
 69+ if ( typeof name == 'undefined' || !desc.type || !desc.label ) {
 70+ $.error( "Missing arguments" );
 71+ }
 72+
 73+ this.$p = $( '<p/>' );
 74+
 75+ this.name = name;
 76+ this.desc = desc;
 77+ }
 78+
 79+ EmptyField.prototype.getName = function() {
 80+ return this.name;
 81+ };
 82+
 83+ EmptyField.prototype.getDesc = function() {
 84+ return this.desc;
 85+ };
 86+
 87+
 88+ //Override expected
 89+ EmptyField.prototype.getValue = function() {
 90+ return null;
 91+ };
 92+
 93+ EmptyField.prototype.getElement = function() {
 94+ return this.$p;
 95+ };
 96+
 97+ EmptyField.prototype.getValidationSettings = function() {
 98+ return {
 99+ rules: {},
 100+ messages: {}
 101+ };
 102+ };
 103+
 104+ //A field with just a label
 105+ LabelField.prototype = object( EmptyField.prototype );
 106+ LabelField.prototype.constructor = LabelField;
 107+ function LabelField( name, desc ) {
 108+ EmptyField.call( this, name, desc );
 109+
 110+ var $label = $( '<label/>' )
 111+ .text( preproc( this.desc.label ) )
 112+ .attr('for', idPrefix + this.name );
 113+
 114+ this.$p.append( $label );
 115+ }
 116+
 117+ //A field with a label and a checkbox
 118+ BooleanField.prototype = object( LabelField.prototype );
 119+ BooleanField.prototype.constructor = BooleanField;
 120+ function BooleanField( name, desc ){
 121+ LabelField.call( this, name, desc );
 122+
 123+ if ( typeof desc.value != 'boolean' ) {
 124+ $.error( "desc.value is invalid" );
 125+ }
 126+
 127+ this.$c = $( '<input/>' )
 128+ .attr( 'type', 'checkbox' )
 129+ .attr( 'id', idPrefix + this.name )
 130+ .attr( 'name', idPrefix + this.name )
 131+ .attr( 'checked', this.desc.value );
 132+
 133+ this.$p.append( this.$c );
 134+ }
 135+
 136+ BooleanField.prototype.getValue = function() {
 137+ return this.$c.is( ':checked' );
 138+ };
 139+
 140+ //A field with a textbox
 141+
 142+ StringField.prototype = object( LabelField.prototype );
 143+ StringField.prototype.constructor = StringField;
 144+ function StringField( name, desc ){
 145+ LabelField.call( this, name, desc );
 146+
 147+ if ( typeof desc.value != 'string' ) {
 148+ $.error( "desc.value is invalid" );
 149+ }
 150+
 151+ this.$text = $( '<input/>' )
 152+ .attr( 'type', 'text' )
 153+ .attr( 'id', idPrefix + this.name )
 154+ .attr( 'name', idPrefix + this.name )
 155+ .val( desc.value );
 156+
 157+ this.$p.append( this.$text );
 158+ }
 159+
 160+ StringField.prototype.getValue = function() {
 161+ return this.$text.val();
 162+ };
 163+
 164+ StringField.prototype.getValidationSettings = function() {
 165+ var settings = LabelField.prototype.getValidationSettings.call( this ),
 166+ fieldId = idPrefix + this.name;
 167+
 168+ settings.rules[fieldId] = {};
 169+ var fieldRules = settings.rules[fieldId],
 170+ desc = this.desc;
 171+
 172+ if ( desc.required === true ) {
 173+ fieldRules.requiredStrict = true;
 174+ }
 175+
 176+ if ( typeof desc.minlength != 'undefined' ) {
 177+ fieldRules.minlengthStrict = desc.minlength;
 178+ }
 179+ if ( typeof desc.maxlength != 'undefined' ) {
 180+ fieldRules.maxlengthStrict = desc.maxlength;
 181+ }
 182+
 183+ settings.messages = {};
 184+
 185+ settings.messages[fieldId] = {
 186+ "minlengthStrict": mw.msg( 'gadgets-formbuilder-minlength', desc.minlength ),
 187+ "maxlengthStrict": mw.msg( 'gadgets-formbuilder-maxlength', desc.maxlength )
 188+ };
 189+
 190+ return settings;
 191+ };
 192+
 193+
 194+ NumberField.prototype = object( LabelField.prototype );
 195+ NumberField.prototype.constructor = NumberField;
 196+ function NumberField( name, desc ){
 197+ LabelField.call( this, name, desc );
 198+
 199+ if ( desc.value !== null && typeof desc.value != 'number' ) {
 200+ $.error( "desc.value is invalid" );
 201+ }
 202+
 203+ this.$text = $( '<input/>' )
 204+ .attr( 'type', 'text' )
 205+ .attr( 'id', idPrefix + this.name )
 206+ .attr( 'name', idPrefix + this.name )
 207+ .val( desc.value );
 208+
 209+ this.$p.append( this.$text );
 210+ }
 211+
 212+ NumberField.prototype.getValue = function() {
 213+ var val = parseFloat( this.$text.val() );
 214+ return isNaN( val ) ? null : val;
 215+ };
 216+
 217+ NumberField.prototype.getValidationSettings = function() {
 218+ var settings = LabelField.prototype.getValidationSettings.call( this ),
 219+ fieldId = idPrefix + this.name;
 220+
 221+ settings.rules[fieldId] = {};
 222+ var fieldRules = settings.rules[fieldId],
 223+ desc = this.desc;
 224+
 225+ if ( desc.required !== false ) {
 226+ fieldRules.requiredStrict = true;
 227+ }
 228+
 229+ if ( desc.integer === true ) {
 230+ fieldRules.integer = true;
 231+ }
 232+
 233+
 234+ if ( typeof desc.min != 'undefined' ) {
 235+ fieldRules.min = desc.min;
 236+ }
 237+ if ( typeof desc.max != 'undefined' ) {
 238+ fieldRules.max = desc.max;
 239+ }
 240+
 241+ settings.messages = {};
 242+
 243+ settings.messages[fieldId] = {
 244+ "required": mw.msg( 'gadgets-formbuilder-required' ),
 245+ "min": mw.msg( 'gadgets-formbuilder-min', desc.min ),
 246+ "max": mw.msg( 'gadgets-formbuilder-max', desc.max )
 247+ };
 248+
 249+ return settings;
 250+ };
 251+
 252+
 253+ SelectField.prototype = object( LabelField.prototype );
 254+ SelectField.prototype.constructor = SelectField;
 255+ function SelectField( name, desc ){
 256+ LabelField.call( this, name, desc );
 257+
 258+ var $select = this.$select = $( '<select/>' )
 259+ .attr( 'id', idPrefix + this.name )
 260+ .attr( 'name', idPrefix + this.name );
 261+
 262+ var values = [];
 263+ $.each( desc.options, function( optName, optVal ) {
 264+ var i = values.length;
 265+ $( '<option/>' )
 266+ .text( preproc( optName ) )
 267+ .val( i )
 268+ .appendTo( $select );
 269+ values.push( optVal );
 270+ } );
 271+
 272+ this.values = values;
 273+
 274+ if ( $.inArray( desc.value, values ) == -1 ) {
 275+ $.error( "desc.value is not in the list of possible values" );
 276+ }
 277+
 278+ var i = $.inArray( desc.value, values );
 279+ $select.val( i ).attr( 'selected', 'selected' );
 280+
 281+ this.$p.append( $select );
 282+ }
 283+
 284+ SelectField.prototype.getValue = function() {
 285+ var i = parseInt( this.$select.val(), 10 );
 286+ return this.values[i];
 287+ };
 288+
 289+
 290+ var validFieldTypes = {
 291+ "boolean": BooleanField,
 292+ "string" : StringField,
 293+ "number" : NumberField,
 294+ "select" : SelectField
 295+ };
 296+
 297+ /* Public methods */
 298+
 299+ /**
 300+ * Main method; takes the given preferences description object and builds
 301+ * the body of the form with the requested fields.
 302+ *
 303+ * @return {Element} the object with the requested form body.
 304+ */
 305+ function buildFormBody() {
 306+ var description = this.get( 0 );
 307+ if ( typeof description != 'object' ) {
 308+ mw.log( "description should be an object, instead of a " + typeof description );
 309+ return null;
 310+ }
 311+
 312+ var $form = $( '<form/>' );
 313+
 314+ //If there is an "intro", adds it to the form as a label
 315+ if ( typeof description.intro == 'string' ) {
 316+ $( '<p/>' )
 317+ .text( preproc( description.intro ) )
 318+ .addClass( 'mw-gadgets-prefsDialog-intro' )
 319+ .appendTo( $form );
 320+ }
 321+
 322+ if ( typeof description.fields != 'object' ) {
 323+ mw.log( "description.fields should be an object, instead of a " + typeof description.fields );
 324+ return null;
 325+ }
 326+
 327+ var fields = [];
 328+
 329+ var settings = {}; //validator settings
 330+
 331+ for ( var fieldName in description.fields ) {
 332+ if ( description.fields.hasOwnProperty( fieldName )) {
 333+ //TODO: validate fieldName
 334+ var field = description.fields[fieldName];
 335+
 336+ var FieldConstructor = validFieldTypes[field.type];
 337+
 338+ if ( typeof FieldConstructor != 'function' ) {
 339+ mw.log( "field with invalid type: " + field.type );
 340+ return null;
 341+ }
 342+
 343+ var f;
 344+ try {
 345+ f = new FieldConstructor( fieldName, field );
 346+ } catch ( e ) {
 347+ mw.log( e );
 348+ return null; //constructor failed, wrong syntax in field description
 349+ }
 350+
 351+ $form.append( f.getElement() );
 352+
 353+ //If this field has validation rules, add them to settings
 354+ var fieldSettings = f.getValidationSettings();
 355+
 356+ if ( fieldSettings ) {
 357+ $.extend( true, settings, fieldSettings );
 358+ }
 359+
 360+ fields.push( f );
 361+ }
 362+ }
 363+
 364+ var validator = $form.validate( settings );
 365+
 366+ $form.data( 'formBuilder', {
 367+ fields: fields,
 368+ validator: validator
 369+ } );
 370+
 371+ return $form;
 372+ }
 373+
 374+ var methods = {
 375+
 376+ /**
 377+ * Returns a dictionary of field names and field values.
 378+ * Returned values are not warranted to pass field validation.
 379+ *
 380+ * @return {Object}
 381+ */
 382+ getValues: function() {
 383+ var data = this.data( 'formBuilder' ),
 384+ result = {};
 385+
 386+ for ( var i = 0; i < data.fields.length; i++ ) {
 387+ var f = data.fields[i];
 388+ result[f.getName()] = f.getValue();
 389+ }
 390+
 391+ return result;
 392+ },
 393+
 394+ /**
 395+ * Do validation of form fields and warn the user about wrong values, if any.
 396+ *
 397+ * @return {Boolean} true if all fields pass validation, false otherwise.
 398+ */
 399+ validate: function() {
 400+ var data = this.data( 'formBuilder' );
 401+ return data.validator.form();
 402+ }
 403+
 404+ };
 405+
 406+ $.fn.formBuilder = function( method ) {
 407+ if ( methods[method] ) {
 408+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
 409+ } else if ( typeof method === 'object' || !method ) {
 410+ return buildFormBody.apply( this, arguments );
 411+ } else {
 412+ $.error( 'Method ' + method + ' does not exist on jQuery.formBuilder' );
 413+ }
 414+ };
 415+})( jQuery, mediaWiki );
 416+
Property changes on: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js
___________________________________________________________________
Added: svn:eol-style
1417 + native
Index: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.css
@@ -0,0 +1,10 @@
 2+/*
 3+ * Styles for gadget's preference setting dialogs.
 4+ */
 5+
 6+#mw-gadgets-prefsDialog label {
 7+ display: block;
 8+ float: left;
 9+ width: 250px;
 10+}
 11+
Property changes on: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.css
___________________________________________________________________
Added: svn:eol-style
112 + native
Index: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js
@@ -0,0 +1,125 @@
 2+/*
 3+ * JavaScript tweaks for Special:Preferences
 4+ */
 5+( function( $, mw ) {
 6+
 7+ //"Save" button click handler
 8+ function saveConfig( $dialog, gadget, config ) {
 9+ var prefsJson = $.toJSON( config );
 10+
 11+ $.ajax( {
 12+ url: mw.config.get( 'wgScriptPath' ) + '/api.php',
 13+ type: "POST",
 14+ data: {
 15+ 'action': 'setgadgetprefs',
 16+ 'gadget': gadget,
 17+ 'prefs': prefsJson,
 18+ 'token': mw.user.tokens.get('editToken'),
 19+ 'format': 'json'
 20+ },
 21+ dataType: "json",
 22+ success: function( response ) {
 23+ if ( typeof response.error == 'undefined' ) {
 24+ alert( mw.msg( 'gadgets-save-success' ) );
 25+ $dialog.dialog( 'close' );
 26+ } else {
 27+ alert( mw.msg( 'gadgets-save-failed' ) );
 28+ }
 29+ },
 30+ error: function( response ) {
 31+ alert( mw.msg( 'gadgets-save-failed' ) );
 32+ }
 33+ } );
 34+ }
 35+
 36+ $( '#mw-htmlform-gadgets input[name="wpgadgets[]"]' ).each( function( idx, input ) {
 37+ var id = input.id,
 38+ gadget = id.substr( "mw-input-wpgadgets-".length );
 39+
 40+ if ( $.inArray( gadget, mw.gadgets.configurableGadgets ) != -1 ) {
 41+ var $span = $( '<span></span>' );
 42+
 43+ if ( !$( input ).is( ':checked' ) ) {
 44+ $span.hide();
 45+ }
 46+
 47+ var $link = $( '<a></a>' )
 48+ .text( mw.msg( 'gadgets-configure' ) )
 49+ .click( function() {
 50+ $.ajax( {
 51+ url: mw.config.get( 'wgScriptPath' ) + '/api.php',
 52+ type: "POST",
 53+ data: {
 54+ 'action': 'getgadgetprefs',
 55+ 'gadget': gadget,
 56+ 'format': 'json'
 57+ },
 58+ dataType: "json", // response type
 59+ success: function( response ) {
 60+
 61+ if ( typeof response.getgadgetprefs != 'object' ) {
 62+ alert( mw.msg( 'gadgets-unexpected-error' ) )
 63+ return;
 64+ }
 65+
 66+ //Create and show dialog
 67+
 68+ var prefs = response.getgadgetprefs;
 69+
 70+ var dialogBody = $( prefs ).formBuilder();
 71+
 72+ $( dialogBody ).submit( function() {
 73+ return false; //prevent form submission
 74+ } );
 75+
 76+ $( dialogBody ).attr( 'id', 'mw-gadgets-prefsDialog' );
 77+
 78+ $( dialogBody ).dialog( {
 79+ modal: true,
 80+ width: 'auto',
 81+ resizable: false,
 82+ title: mw.msg( 'gadgets-configuration-of', gadget ),
 83+ close: function() {
 84+ $( this ).dialog( 'destroy' ).empty(); //completely destroy on close
 85+ },
 86+ buttons: [
 87+ //TODO: add a "Restore defaults" button
 88+ {
 89+ text: mw.msg( 'gadgets-prefs-save' ),
 90+ click: function() {
 91+ var isValid = $( dialogBody ).formBuilder( 'validate' );
 92+
 93+ if ( isValid ) {
 94+ var values = $( dialogBody ).formBuilder( 'getValues' );
 95+ saveConfig( $( this ), gadget, values );
 96+ }
 97+ }
 98+ },
 99+ {
 100+ text: mw.msg( 'gadgets-prefs-cancel' ),
 101+ click: function() {
 102+ $( this ).dialog( "close" );
 103+ }
 104+ }
 105+ ]
 106+ } );
 107+ },
 108+ error: function( response ) {
 109+ alert( mw.msg( 'gadgets-unexpected-error' ) );
 110+ }
 111+ } );
 112+
 113+ return false; //prevent event propagation
 114+ } );
 115+
 116+ $span.append( "&nbsp;·&nbsp;", $link );
 117+ $( input ).next().append( $span );
 118+
 119+ //Toggle visibility on click to the input
 120+ $( input ).click( function() {
 121+ $span.fadeToggle( 'fast' );
 122+ } );
 123+ }
 124+ } );
 125+} )( jQuery, mediaWiki );
 126+
Property changes on: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js
___________________________________________________________________
Added: svn:eol-style
1127 + native

Status & tagging log