Index: branches/Gadgets-work/Gadgets_body.php |
— | — | @@ -12,9 +12,18 @@ |
13 | 13 | * @license GNU General Public Licence 2.0 or later |
14 | 14 | */ |
15 | 15 | |
16 | | -class Gadgets { |
| 16 | +// @todo: Support specifying RL-awareness per gadget |
17 | 17 | |
18 | | - public static function ArticleSaveComplete( $article, $user, $text ) { |
| 18 | + class GadgetHooks { |
| 19 | + |
| 20 | + /** |
| 21 | + * ArticleSaveComplete hook handler. |
| 22 | + * |
| 23 | + * @param $article Article |
| 24 | + * @param $user User |
| 25 | + * @param $text String: New page text |
| 26 | + */ |
| 27 | + public static function articleSaveComplete( $article, $user, $text ) { |
19 | 28 | //update cache if MediaWiki:Gadgets-definition was edited |
20 | 29 | $title = $article->mTitle; |
21 | 30 | if( $title->getNamespace() == NS_MEDIAWIKI && $title->getText() == 'Gadgets-definition' ) { |
— | — | @@ -23,6 +32,11 @@ |
24 | 33 | return true; |
25 | 34 | } |
26 | 35 | |
| 36 | + /** |
| 37 | + * GetPreferences hook handler. |
| 38 | + * @param $user User |
| 39 | + * @param $preferences Array: Preference descriptions |
| 40 | + */ |
27 | 41 | public static function getPreferences( $user, &$preferences ) { |
28 | 42 | $gadgets = Gadget::loadStructuredList(); |
29 | 43 | if (!$gadgets) return true; |
— | — | @@ -66,6 +80,10 @@ |
67 | 81 | return true; |
68 | 82 | } |
69 | 83 | |
| 84 | + /** |
| 85 | + * ResourceLoaderRegisterModules hook handler. |
| 86 | + * @param $resourceLoader ResourceLoader |
| 87 | + */ |
70 | 88 | public static function registerModules( &$resourceLoader ) { |
71 | 89 | $gadgets = Gadget::loadList(); |
72 | 90 | if ( !$gadgets ) { |
— | — | @@ -80,6 +98,10 @@ |
81 | 99 | return true; |
82 | 100 | } |
83 | 101 | |
| 102 | + /** |
| 103 | + * BeforePageDisplay hook handler. |
| 104 | + * @param $out OutputPage |
| 105 | + */ |
84 | 106 | public static function beforePageDisplay( $out ) { |
85 | 107 | global $wgUser; |
86 | 108 | if ( !$wgUser->isLoggedIn() ) return true; |
— | — | @@ -122,16 +144,22 @@ |
123 | 145 | foreach ( $pages as $page ) { |
124 | 146 | if ( isset( $done[$page] ) ) continue; |
125 | 147 | $done[$page] = true; |
126 | | - self::applyGadgetCode( $page, $out ); |
| 148 | + self::applyScript( $page, $out ); |
127 | 149 | } |
128 | 150 | |
129 | 151 | return true; |
130 | 152 | } |
131 | 153 | |
132 | | - private static function applyGadgetCode( $page, $out ) { |
| 154 | + /** |
| 155 | + * Adds one legacy script to output. |
| 156 | + * |
| 157 | + * @param $page String: Unprefixed page title |
| 158 | + * @param $out OutputPage |
| 159 | + */ |
| 160 | + private static function applyScript( $page, $out ) { |
133 | 161 | global $wgJsMimeType; |
134 | 162 | |
135 | | - //FIXME: stuff added via $out->addScript appears below usercss and userjs in the head tag. |
| 163 | + //FIXME: stuff added via $out->addScript appears below usercss and userjs |
136 | 164 | // but we'd want it to appear above explicit user stuff, so it can be overwritten. |
137 | 165 | |
138 | 166 | $t = Title::makeTitleSafe( NS_MEDIAWIKI, $page ); |
— | — | @@ -143,8 +171,14 @@ |
144 | 172 | } |
145 | 173 | } |
146 | 174 | |
| 175 | +/** |
| 176 | + * Wrapper for one gadget. |
| 177 | + */ |
147 | 178 | class Gadget { |
148 | | - const GADGET_CLASS_VERSION = 1; // Increment this when changing fields |
| 179 | + /** |
| 180 | + * Increment this when changing class structure |
| 181 | + */ |
| 182 | + const GADGET_CLASS_VERSION = 1; |
149 | 183 | |
150 | 184 | private $version = self::GADGET_CLASS_VERSION, |
151 | 185 | $scripts = array(), |
— | — | @@ -152,6 +186,11 @@ |
153 | 187 | $name, |
154 | 188 | $resourceLoaded = false; |
155 | 189 | |
| 190 | + /** |
| 191 | + * Creates an instance of this class from definition in MediaWiki:Gadgets-definition |
| 192 | + * @param $definition String: Gadget definition |
| 193 | + * @return Mixed: Instance of Gadget class or false if $definition is invalid |
| 194 | + */ |
156 | 195 | public static function newFromDefinition( $definition ) { |
157 | 196 | if ( !preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)\s*((\|[^|]*)+)\s*$/', $definition, $m ) ) { |
158 | 197 | return false; |
— | — | @@ -172,40 +211,70 @@ |
173 | 212 | return $gadget; |
174 | 213 | } |
175 | 214 | |
| 215 | + /** |
| 216 | + * @return String: Gadget name |
| 217 | + */ |
176 | 218 | public function getName() { |
177 | 219 | return $this->name; |
178 | 220 | } |
179 | 221 | |
| 222 | + /** |
| 223 | + * @return String: Name of ResourceLoader module for this gadget |
| 224 | + */ |
180 | 225 | public function getModuleName() { |
181 | 226 | return "ext.gadget.{$this->name}"; |
182 | 227 | } |
183 | 228 | |
| 229 | + /** |
| 230 | + * Checks whether this is an instance of an older version of this class deserialized from cache |
| 231 | + * @return Boolean |
| 232 | + */ |
184 | 233 | public function isOutdated() { |
185 | 234 | return $this->version != GADGET_CLASS_VERSION; |
186 | 235 | } |
187 | 236 | |
| 237 | + /** |
| 238 | + * @return Boolean: Whether all of this gadget's JS components support ResourceLoader |
| 239 | + */ |
188 | 240 | public function supportsResourceLoader() { |
189 | 241 | return $this->resourceLoaded; |
190 | 242 | } |
191 | 243 | |
| 244 | + /** |
| 245 | + * @return Boolean: Whether this gadget has resources that can be loaded via ResourceLoader |
| 246 | + */ |
192 | 247 | public function hasModule() { |
193 | 248 | return count( $this->styles ) |
194 | 249 | + ( $this->supportsResourceLoader() ? count( $this->scripts ) : 0 ) |
195 | 250 | > 0; |
196 | 251 | } |
197 | 252 | |
| 253 | + /** |
| 254 | + * @return Array: Array of pages with JS not prefixed with namespace |
| 255 | + */ |
198 | 256 | public function getScripts() { |
199 | 257 | return $this->scripts; |
200 | 258 | } |
201 | 259 | |
| 260 | + /** |
| 261 | + * @return Array: Array of pages with CSS not prefixed with namespace |
| 262 | + */ |
202 | 263 | public function getStyles() { |
203 | 264 | return $this->styles; |
204 | 265 | } |
205 | 266 | |
| 267 | + /** |
| 268 | + * @return Array: Array of all of this gadget's resources |
| 269 | + */ |
206 | 270 | public function getScriptsAndStyles() { |
207 | 271 | return array_merge( $this->scripts, $this->styles ); |
208 | 272 | } |
209 | 273 | |
| 274 | + /** |
| 275 | + * Returns module for ResourceLoader, see getModuleName() for its name. |
| 276 | + * If our gadget has no scripts or styles suitable for RL, false will be returned. |
| 277 | + * @return Mixed: GadgetResourceLoaderModule or false |
| 278 | + */ |
210 | 279 | public function getModule() { |
211 | 280 | $pages = array(); |
212 | 281 | foreach( $this->styles as $style ) { |
— | — | @@ -222,6 +291,10 @@ |
223 | 292 | return new GadgetResourceLoaderModule( $pages ); |
224 | 293 | } |
225 | 294 | |
| 295 | + /** |
| 296 | + * Returns list of scripts that don't support ResourceLoader |
| 297 | + * @return Array |
| 298 | + */ |
226 | 299 | public function getLegacyScripts() { |
227 | 300 | if ( $this->supportsResourceLoader() ) { |
228 | 301 | return array(); |
— | — | @@ -229,6 +302,10 @@ |
230 | 303 | return $this->scripts; |
231 | 304 | } |
232 | 305 | |
| 306 | + /** |
| 307 | + * Loads and returns a list of all gadgets |
| 308 | + * @return Mixed: Array of gadgets or false |
| 309 | + */ |
233 | 310 | public static function loadList() { |
234 | 311 | static $gadgets = null; |
235 | 312 | |
— | — | @@ -248,6 +325,34 @@ |
249 | 326 | return $gadgets; |
250 | 327 | } |
251 | 328 | |
| 329 | + /** |
| 330 | + * Checks whether gadget list from cache can be used. |
| 331 | + * @return Boolean |
| 332 | + */ |
| 333 | + private static function isValidList( $gadgets ) { |
| 334 | + if ( !is_array( $gadgets ) ) return false; |
| 335 | + // Check if we have 1) array of gadgets 2) the gadgets are up to date |
| 336 | + // One check is enough |
| 337 | + foreach ( $gadgets as $section => $list ) { |
| 338 | + foreach ( $list as $g ) { |
| 339 | + if ( !( $g instanceof Gadget ) || $g->isOutdated() ) { |
| 340 | + return false; |
| 341 | + } else { |
| 342 | + return true; |
| 343 | + } |
| 344 | + } |
| 345 | + } |
| 346 | + return true; // empty array |
| 347 | + } |
| 348 | + |
| 349 | + /** |
| 350 | + * Loads list of gadgets and returns it as associative array of sections with gadgets |
| 351 | + * e.g. array( 'sectionnname1' => array( $gadget1, $gadget2), |
| 352 | + * 'sectionnname2' => array( $gadget3 ) ); |
| 353 | + * @param $forceNewText String: New text of MediaWiki:gadgets-sdefinition. If specified, will |
| 354 | + * force a purge of cache and recreation of the gadget list. |
| 355 | + * @return Mixed: Array or false |
| 356 | + */ |
252 | 357 | public static function loadStructuredList( $forceNewText = null ) { |
253 | 358 | global $wgMemc; |
254 | 359 | |
— | — | @@ -259,8 +364,7 @@ |
260 | 365 | if ( $forceNewText === null ) { |
261 | 366 | //cached? |
262 | 367 | $gadgets = $wgMemc->get( $key ); |
263 | | - // TODO: isOutdated() |
264 | | - if ( is_array($gadgets) && next( $gadgets ) instanceof Gadget ) return $gadgets; |
| 368 | + if ( self::isValidList( $gadgets ) ) return $gadgets; |
265 | 369 | |
266 | 370 | $g = wfMsgForContentNoTrans( "gadgets-definition" ); |
267 | 371 | if ( wfEmptyMsg( "gadgets-definition", $g ) ) { |
— | — | @@ -282,7 +386,7 @@ |
283 | 387 | $section = $m[1]; |
284 | 388 | } |
285 | 389 | else { |
286 | | - $gadget = Gadget::newFromDefinition( $line ); |
| 390 | + $gadget = self::newFromDefinition( $line ); |
287 | 391 | if ( $gadget ) { |
288 | 392 | $gadgets[$section][$gadget->getName()] = $gadget; |
289 | 393 | } |
— | — | @@ -298,13 +402,29 @@ |
299 | 403 | } |
300 | 404 | } |
301 | 405 | |
| 406 | +/** |
| 407 | + * Class representing a list of resources for one gadget |
| 408 | + */ |
302 | 409 | class GadgetResourceLoaderModule extends ResourceLoaderWikiModule { |
303 | 410 | private $pages; |
304 | 411 | |
| 412 | + /** |
| 413 | + * Creates an instance of this class |
| 414 | + * @param $pages Array: Associative array of pages in ResourceLoaderWikiModule-compatible |
| 415 | + * format, for example: |
| 416 | + * array( |
| 417 | + * 'Gadget-foo.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ), |
| 418 | + * 'Gadget-foo.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ), |
| 419 | + * ) |
| 420 | + */ |
305 | 421 | public function __construct( $pages ) { |
306 | 422 | $this->pages = $pages; |
307 | 423 | } |
308 | 424 | |
| 425 | + /** |
| 426 | + * Overrides the abstract function from ResourceLoaderWikiModule class |
| 427 | + * @return Array: $pages passed to __construct() |
| 428 | + */ |
309 | 429 | protected function getPages( ResourceLoaderContext $context ) { |
310 | 430 | return $this->pages; |
311 | 431 | } |
Index: branches/Gadgets-work/Gadgets.php |
— | — | @@ -29,17 +29,17 @@ |
30 | 30 | 'descriptionmsg' => 'gadgets-desc', |
31 | 31 | ); |
32 | 32 | |
33 | | -$wgHooks['ArticleSaveComplete'][] = 'Gadgets::articleSaveComplete'; |
34 | | -$wgHooks['BeforePageDisplay'][] = 'Gadgets::beforePageDisplay'; |
35 | | -$wgHooks['GetPreferences'][] = 'Gadgets::getPreferences'; |
36 | | -$wgHooks['ResourceLoaderRegisterModules'][] = 'Gadgets::registerModules'; |
| 33 | +$wgHooks['ArticleSaveComplete'][] = 'GadgetHooks::articleSaveComplete'; |
| 34 | +$wgHooks['BeforePageDisplay'][] = 'GadgetHooks::beforePageDisplay'; |
| 35 | +$wgHooks['GetPreferences'][] = 'GadgetHooks::getPreferences'; |
| 36 | +$wgHooks['ResourceLoaderRegisterModules'][] = 'GadgetHooks::registerModules'; |
37 | 37 | |
38 | 38 | $dir = dirname(__FILE__) . '/'; |
39 | 39 | $wgExtensionMessagesFiles['Gadgets'] = $dir . 'Gadgets.i18n.php'; |
40 | 40 | $wgExtensionAliasesFiles['Gadgets'] = $dir . 'Gadgets.alias.php'; |
41 | 41 | |
42 | 42 | $wgAutoloadClasses['Gadget'] = $dir . 'Gadgets_body.php'; |
43 | | -$wgAutoloadClasses['Gadgets'] = $dir . 'Gadgets_body.php'; |
| 43 | +$wgAutoloadClasses['GadgetHooks'] = $dir . 'Gadgets_body.php'; |
44 | 44 | $wgAutoloadClasses['GadgetsResourceLoaderModule'] = $dir . 'Gadgets_body.php'; |
45 | 45 | $wgAutoloadClasses['SpecialGadgets'] = $dir . 'SpecialGadgets.php'; |
46 | 46 | |