r75024 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r75023‎ | r75024 | r75025 >
Date:18:31, 19 October 2010
Author:tparscal
Status:ok (Comments)
Tags:
Comment:
Part 2 of 2, moved ResourceLoader*Module classes to their own files - this commit removes the non file specific code.
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderModule.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderSiteModule.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderStartUpModule.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderUserModule.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderUserOptionsModule.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderWikiModule.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -23,815 +23,6 @@
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
2626 /**
27 - * Abstraction for resource loader modules, with name registration and maxage functionality.
28 - */
29 -abstract class ResourceLoaderModule {
30 -
31 - /* Protected Members */
32 -
33 - protected $name = null;
34 -
35 - // In-object cache for file dependencies
36 - protected $fileDeps = array();
37 - // In-object cache for message blob mtime
38 - protected $msgBlobMtime = array();
39 -
40 - /* Methods */
41 -
42 - /**
43 - * Get this module's name. This is set when the module is registered
44 - * with ResourceLoader::register()
45 - *
46 - * @return Mixed: name (string) or null if no name was set
47 - */
48 - public function getName() {
49 - return $this->name;
50 - }
51 -
52 - /**
53 - * Set this module's name. This is called by ResourceLodaer::register()
54 - * when registering the module. Other code should not call this.
55 - *
56 - * @param $name String: name
57 - */
58 - public function setName( $name ) {
59 - $this->name = $name;
60 - }
61 -
62 - /**
63 - * Get whether CSS for this module should be flipped
64 - */
65 - public function getFlip( $context ) {
66 - return $context->getDirection() === 'rtl';
67 - }
68 -
69 - /**
70 - * Get all JS for this module for a given language and skin.
71 - * Includes all relevant JS except loader scripts.
72 - *
73 - * @param $context ResourceLoaderContext object
74 - * @return String: JS
75 - */
76 - public function getScript( ResourceLoaderContext $context ) {
77 - // Stub, override expected
78 - return '';
79 - }
80 -
81 - /**
82 - * Get all CSS for this module for a given skin.
83 - *
84 - * @param $context ResourceLoaderContext object
85 - * @return array: strings of CSS keyed by media type
86 - */
87 - public function getStyles( ResourceLoaderContext $context ) {
88 - // Stub, override expected
89 - return '';
90 - }
91 -
92 - /**
93 - * Get the messages needed for this module.
94 - *
95 - * To get a JSON blob with messages, use MessageBlobStore::get()
96 - *
97 - * @return array of message keys. Keys may occur more than once
98 - */
99 - public function getMessages() {
100 - // Stub, override expected
101 - return array();
102 - }
103 -
104 - /**
105 - * Get the group this module is in.
106 - *
107 - * @return string of group name
108 - */
109 - public function getGroup() {
110 - // Stub, override expected
111 - return null;
112 - }
113 -
114 - /**
115 - * Get the loader JS for this module, if set.
116 - *
117 - * @return Mixed: loader JS (string) or false if no custom loader set
118 - */
119 - public function getLoaderScript() {
120 - // Stub, override expected
121 - return false;
122 - }
123 -
124 - /**
125 - * Get a list of modules this module depends on.
126 - *
127 - * Dependency information is taken into account when loading a module
128 - * on the client side. When adding a module on the server side,
129 - * dependency information is NOT taken into account and YOU are
130 - * responsible for adding dependent modules as well. If you don't do
131 - * this, the client side loader will send a second request back to the
132 - * server to fetch the missing modules, which kind of defeats the
133 - * purpose of the resource loader.
134 - *
135 - * To add dependencies dynamically on the client side, use a custom
136 - * loader script, see getLoaderScript()
137 - * @return Array of module names (strings)
138 - */
139 - public function getDependencies() {
140 - // Stub, override expected
141 - return array();
142 - }
143 -
144 - /**
145 - * Get the files this module depends on indirectly for a given skin.
146 - * Currently these are only image files referenced by the module's CSS.
147 - *
148 - * @param $skin String: skin name
149 - * @return array of files
150 - */
151 - public function getFileDependencies( $skin ) {
152 - // Try in-object cache first
153 - if ( isset( $this->fileDeps[$skin] ) ) {
154 - return $this->fileDeps[$skin];
155 - }
156 -
157 - $dbr = wfGetDB( DB_SLAVE );
158 - $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
159 - 'md_module' => $this->getName(),
160 - 'md_skin' => $skin,
161 - ), __METHOD__
162 - );
163 - if ( !is_null( $deps ) ) {
164 - return $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
165 - }
166 - return $this->fileDeps[$skin] = array();
167 - }
168 -
169 - /**
170 - * Set preloaded file dependency information. Used so we can load this
171 - * information for all modules at once.
172 - * @param $skin string Skin name
173 - * @param $deps array Array of file names
174 - */
175 - public function setFileDependencies( $skin, $deps ) {
176 - $this->fileDeps[$skin] = $deps;
177 - }
178 -
179 - /**
180 - * Get the last modification timestamp of the message blob for this
181 - * module in a given language.
182 - * @param $lang string Language code
183 - * @return int UNIX timestamp, or 0 if no blob found
184 - */
185 - public function getMsgBlobMtime( $lang ) {
186 - if ( !count( $this->getMessages() ) )
187 - return 0;
188 -
189 - $dbr = wfGetDB( DB_SLAVE );
190 - $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
191 - 'mr_resource' => $this->getName(),
192 - 'mr_lang' => $lang
193 - ), __METHOD__
194 - );
195 - $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
196 - return $this->msgBlobMtime[$lang];
197 - }
198 -
199 - /**
200 - * Set a preloaded message blob last modification timestamp. Used so we
201 - * can load this information for all modules at once.
202 - * @param $lang string Language code
203 - * @param $mtime int UNIX timestamp or 0 if there is no such blob
204 - */
205 - public function setMsgBlobMtime( $lang, $mtime ) {
206 - $this->msgBlobMtime[$lang] = $mtime;
207 - }
208 -
209 - /* Abstract Methods */
210 -
211 - /**
212 - * Get this module's last modification timestamp for a given
213 - * combination of language, skin and debug mode flag. This is typically
214 - * the highest of each of the relevant components' modification
215 - * timestamps. Whenever anything happens that changes the module's
216 - * contents for these parameters, the mtime should increase.
217 - *
218 - * @param $context ResourceLoaderContext object
219 - * @return int UNIX timestamp
220 - */
221 - public function getModifiedTime( ResourceLoaderContext $context ) {
222 - // 0 would mean now
223 - return 1;
224 - }
225 -}
226 -
227 -/**
228 - * Module based on local JS/CSS files. This is the most common type of module.
229 - */
230 -class ResourceLoaderFileModule extends ResourceLoaderModule {
231 - /* Protected Members */
232 -
233 - protected $scripts = array();
234 - protected $styles = array();
235 - protected $messages = array();
236 - protected $group;
237 - protected $dependencies = array();
238 - protected $debugScripts = array();
239 - protected $languageScripts = array();
240 - protected $skinScripts = array();
241 - protected $skinStyles = array();
242 - protected $loaders = array();
243 - protected $parameters = array();
244 -
245 - // In-object cache for file dependencies
246 - protected $fileDeps = array();
247 - // In-object cache for mtime
248 - protected $modifiedTime = array();
249 -
250 - /* Methods */
251 -
252 - /**
253 - * Construct a new module from an options array.
254 - *
255 - * @param $options array Options array. If empty, an empty module will be constructed
256 - *
257 - * $options format:
258 - * array(
259 - * // Required module options (mutually exclusive)
260 - * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ),
261 - *
262 - * // Optional module options
263 - * 'languageScripts' => array(
264 - * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... )
265 - * ...
266 - * ),
267 - * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ),
268 - * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ),
269 - *
270 - * // Non-raw module options
271 - * 'dependencies' => 'module' | array( 'module1', 'module2' ... )
272 - * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ),
273 - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), |
274 - * array( 'dir/file1.css' => array( 'media' => 'print' ) ),
275 - * 'skinStyles' => array(
276 - * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) |
277 - * array( 'dir/file1.css' => array( 'media' => 'print' )
278 - * ...
279 - * ),
280 - * 'messages' => array( 'message1', 'message2' ... ),
281 - * 'group' => 'stuff',
282 - * )
283 - *
284 - * @param $basePath String: base path to prepend to all paths in $options
285 - */
286 - public function __construct( $options = array(), $basePath = null ) {
287 - foreach ( $options as $option => $value ) {
288 - switch ( $option ) {
289 - case 'scripts':
290 - case 'debugScripts':
291 - case 'languageScripts':
292 - case 'skinScripts':
293 - case 'loaders':
294 - $this->{$option} = (array)$value;
295 - // Automatically prefix script paths
296 - if ( is_string( $basePath ) ) {
297 - foreach ( $this->{$option} as $key => $value ) {
298 - $this->{$option}[$key] = $basePath . $value;
299 - }
300 - }
301 - break;
302 - case 'styles':
303 - case 'skinStyles':
304 - $this->{$option} = (array)$value;
305 - // Automatically prefix style paths
306 - if ( is_string( $basePath ) ) {
307 - foreach ( $this->{$option} as $key => $value ) {
308 - if ( is_array( $value ) ) {
309 - $this->{$option}[$basePath . $key] = $value;
310 - unset( $this->{$option}[$key] );
311 - } else {
312 - $this->{$option}[$key] = $basePath . $value;
313 - }
314 - }
315 - }
316 - break;
317 - case 'dependencies':
318 - case 'messages':
319 - $this->{$option} = (array)$value;
320 - break;
321 - case 'group':
322 - $this->group = (string)$value;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - /**
329 - * Add script files to this module. In order to be valid, a module
330 - * must contain at least one script file.
331 - *
332 - * @param $scripts Mixed: path to script file (string) or array of paths
333 - */
334 - public function addScripts( $scripts ) {
335 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
336 - }
337 -
338 - /**
339 - * Add style (CSS) files to this module.
340 - *
341 - * @param $styles Mixed: path to CSS file (string) or array of paths
342 - */
343 - public function addStyles( $styles ) {
344 - $this->styles = array_merge( $this->styles, (array)$styles );
345 - }
346 -
347 - /**
348 - * Add messages to this module.
349 - *
350 - * @param $messages Mixed: message key (string) or array of message keys
351 - */
352 - public function addMessages( $messages ) {
353 - $this->messages = array_merge( $this->messages, (array)$messages );
354 - }
355 -
356 - /**
357 - * Sets the group of this module.
358 - *
359 - * @param $group string group name
360 - */
361 - public function setGroup( $group ) {
362 - $this->group = $group;
363 - }
364 -
365 - /**
366 - * Add dependencies. Dependency information is taken into account when
367 - * loading a module on the client side. When adding a module on the
368 - * server side, dependency information is NOT taken into account and
369 - * YOU are responsible for adding dependent modules as well. If you
370 - * don't do this, the client side loader will send a second request
371 - * back to the server to fetch the missing modules, which kind of
372 - * defeats the point of using the resource loader in the first place.
373 - *
374 - * To add dependencies dynamically on the client side, use a custom
375 - * loader (see addLoaders())
376 - *
377 - * @param $dependencies Mixed: module name (string) or array of module names
378 - */
379 - public function addDependencies( $dependencies ) {
380 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
381 - }
382 -
383 - /**
384 - * Add debug scripts to the module. These scripts are only included
385 - * in debug mode.
386 - *
387 - * @param $scripts Mixed: path to script file (string) or array of paths
388 - */
389 - public function addDebugScripts( $scripts ) {
390 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
391 - }
392 -
393 - /**
394 - * Add language-specific scripts. These scripts are only included for
395 - * a given language.
396 - *
397 - * @param $lang String: language code
398 - * @param $scripts Mixed: path to script file (string) or array of paths
399 - */
400 - public function addLanguageScripts( $lang, $scripts ) {
401 - $this->languageScripts = array_merge_recursive(
402 - $this->languageScripts,
403 - array( $lang => $scripts )
404 - );
405 - }
406 -
407 - /**
408 - * Add skin-specific scripts. These scripts are only included for
409 - * a given skin.
410 - *
411 - * @param $skin String: skin name, or 'default'
412 - * @param $scripts Mixed: path to script file (string) or array of paths
413 - */
414 - public function addSkinScripts( $skin, $scripts ) {
415 - $this->skinScripts = array_merge_recursive(
416 - $this->skinScripts,
417 - array( $skin => $scripts )
418 - );
419 - }
420 -
421 - /**
422 - * Add skin-specific CSS. These CSS files are only included for a
423 - * given skin. If there are no skin-specific CSS files for a skin,
424 - * the files defined for 'default' will be used, if any.
425 - *
426 - * @param $skin String: skin name, or 'default'
427 - * @param $scripts Mixed: path to CSS file (string) or array of paths
428 - */
429 - public function addSkinStyles( $skin, $scripts ) {
430 - $this->skinStyles = array_merge_recursive(
431 - $this->skinStyles,
432 - array( $skin => $scripts )
433 - );
434 - }
435 -
436 - /**
437 - * Add loader scripts. These scripts are loaded on every page and are
438 - * responsible for registering this module using
439 - * mediaWiki.loader.register(). If there are no loader scripts defined,
440 - * the resource loader will register the module itself.
441 - *
442 - * Loader scripts are used to determine a module's dependencies
443 - * dynamically on the client side (e.g. based on browser type/version).
444 - * Note that loader scripts are included on every page, so they should
445 - * be lightweight and use mediaWiki.loader.register()'s callback
446 - * feature to defer dependency calculation.
447 - *
448 - * @param $scripts Mixed: path to script file (string) or array of paths
449 - */
450 - public function addLoaders( $scripts ) {
451 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
452 - }
453 -
454 - public function getScript( ResourceLoaderContext $context ) {
455 - $retval = $this->getPrimaryScript() . "\n" .
456 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
457 - $this->getSkinScript( $context->getSkin() );
458 -
459 - if ( $context->getDebug() ) {
460 - $retval .= $this->getDebugScript();
461 - }
462 -
463 - return $retval;
464 - }
465 -
466 - public function getStyles( ResourceLoaderContext $context ) {
467 - $styles = array();
468 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
469 - if ( !isset( $styles[$media] ) ) {
470 - $styles[$media] = '';
471 - }
472 - $styles[$media] .= $style;
473 - }
474 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
475 - if ( !isset( $styles[$media] ) ) {
476 - $styles[$media] = '';
477 - }
478 - $styles[$media] .= $style;
479 - }
480 -
481 - // Collect referenced files
482 - $files = array();
483 - foreach ( $styles as $style ) {
484 - // Extract and store the list of referenced files
485 - $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
486 - }
487 -
488 - // Only store if modified
489 - if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
490 - $encFiles = FormatJson::encode( $files );
491 - $dbw = wfGetDB( DB_MASTER );
492 - $dbw->replace( 'module_deps',
493 - array( array( 'md_module', 'md_skin' ) ), array(
494 - 'md_module' => $this->getName(),
495 - 'md_skin' => $context->getSkin(),
496 - 'md_deps' => $encFiles,
497 - )
498 - );
499 - }
500 -
501 - return $styles;
502 - }
503 -
504 - public function getMessages() {
505 - return $this->messages;
506 - }
507 -
508 - public function getGroup() {
509 - return $this->group;
510 - }
511 -
512 - public function getDependencies() {
513 - return $this->dependencies;
514 - }
515 -
516 - public function getLoaderScript() {
517 - if ( count( $this->loaders ) == 0 ) {
518 - return false;
519 - }
520 -
521 - return self::concatScripts( $this->loaders );
522 - }
523 -
524 - /**
525 - * Get the last modified timestamp of this module, which is calculated
526 - * as the highest last modified timestamp of its constituent files and
527 - * the files it depends on (see getFileDependencies()). Only files
528 - * relevant to the given language and skin are taken into account, and
529 - * files only relevant in debug mode are not taken into account when
530 - * debug mode is off.
531 - *
532 - * @param $context ResourceLoaderContext object
533 - * @return Integer: UNIX timestamp
534 - */
535 - public function getModifiedTime( ResourceLoaderContext $context ) {
536 - if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
537 - return $this->modifiedTime[$context->getHash()];
538 - }
539 - wfProfileIn( __METHOD__ );
540 -
541 - // Sort of nasty way we can get a flat list of files depended on by all styles
542 - $styles = array();
543 - foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
544 - $styles = array_merge( $styles, $styleFiles );
545 - }
546 - $skinFiles = (array) self::getSkinFiles(
547 - $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' )
548 - );
549 - foreach ( $skinFiles as $styleFiles ) {
550 - $styles = array_merge( $styles, $styleFiles );
551 - }
552 -
553 - // Final merge, this should result in a master list of dependent files
554 - $files = array_merge(
555 - $this->scripts,
556 - $styles,
557 - $context->getDebug() ? $this->debugScripts : array(),
558 - isset( $this->languageScripts[$context->getLanguage()] ) ?
559 - (array) $this->languageScripts[$context->getLanguage()] : array(),
560 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
561 - $this->loaders,
562 - $this->getFileDependencies( $context->getSkin() )
563 - );
564 -
565 - wfProfileIn( __METHOD__.'-filemtime' );
566 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
567 - wfProfileOut( __METHOD__.'-filemtime' );
568 - $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
569 - wfProfileOut( __METHOD__ );
570 - return $this->modifiedTime[$context->getHash()];
571 - }
572 -
573 - /* Protected Members */
574 -
575 - /**
576 - * Get the primary JS for this module. This is pulled from the
577 - * script files added through addScripts()
578 - *
579 - * @return String: JS
580 - */
581 - protected function getPrimaryScript() {
582 - return self::concatScripts( $this->scripts );
583 - }
584 -
585 - /**
586 - * Get the primary CSS for this module. This is pulled from the CSS
587 - * files added through addStyles()
588 - *
589 - * @return Array
590 - */
591 - protected function getPrimaryStyles() {
592 - return self::concatStyles( $this->styles );
593 - }
594 -
595 - /**
596 - * Get the debug JS for this module. This is pulled from the script
597 - * files added through addDebugScripts()
598 - *
599 - * @return String: JS
600 - */
601 - protected function getDebugScript() {
602 - return self::concatScripts( $this->debugScripts );
603 - }
604 -
605 - /**
606 - * Get the language-specific JS for a given language. This is pulled
607 - * from the language-specific script files added through addLanguageScripts()
608 - *
609 - * @return String: JS
610 - */
611 - protected function getLanguageScript( $lang ) {
612 - if ( !isset( $this->languageScripts[$lang] ) ) {
613 - return '';
614 - }
615 - return self::concatScripts( $this->languageScripts[$lang] );
616 - }
617 -
618 - /**
619 - * Get the skin-specific JS for a given skin. This is pulled from the
620 - * skin-specific JS files added through addSkinScripts()
621 - *
622 - * @return String: JS
623 - */
624 - protected function getSkinScript( $skin ) {
625 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
626 - }
627 -
628 - /**
629 - * Get the skin-specific CSS for a given skin. This is pulled from the
630 - * skin-specific CSS files added through addSkinStyles()
631 - *
632 - * @return Array: list of CSS strings keyed by media type
633 - */
634 - protected function getSkinStyles( $skin ) {
635 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
636 - }
637 -
638 - /**
639 - * Helper function to get skin-specific data from an array.
640 - *
641 - * @param $skin String: skin name
642 - * @param $map Array: map of skin names to arrays
643 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
644 - */
645 - protected static function getSkinFiles( $skin, $map ) {
646 - $retval = array();
647 -
648 - if ( isset( $map[$skin] ) && $map[$skin] ) {
649 - $retval = $map[$skin];
650 - } else if ( isset( $map['default'] ) ) {
651 - $retval = $map['default'];
652 - }
653 -
654 - return $retval;
655 - }
656 -
657 - /**
658 - * Get the contents of a set of files and concatenate them, with
659 - * newlines in between. Each file is used only once.
660 - *
661 - * @param $files Array of file names
662 - * @return String: concatenated contents of $files
663 - */
664 - protected static function concatScripts( $files ) {
665 - return implode( "\n",
666 - array_map(
667 - 'file_get_contents',
668 - array_map(
669 - array( __CLASS__, 'remapFilename' ),
670 - array_unique( (array) $files ) ) ) );
671 - }
672 -
673 - protected static function organizeFilesByOption( $files, $option, $default ) {
674 - $organizedFiles = array();
675 - foreach ( (array) $files as $key => $value ) {
676 - if ( is_int( $key ) ) {
677 - // File name as the value
678 - if ( !isset( $organizedFiles[$default] ) ) {
679 - $organizedFiles[$default] = array();
680 - }
681 - $organizedFiles[$default][] = $value;
682 - } else if ( is_array( $value ) ) {
683 - // File name as the key, options array as the value
684 - $media = isset( $value[$option] ) ? $value[$option] : $default;
685 - if ( !isset( $organizedFiles[$media] ) ) {
686 - $organizedFiles[$media] = array();
687 - }
688 - $organizedFiles[$media][] = $key;
689 - }
690 - }
691 - return $organizedFiles;
692 - }
693 -
694 - /**
695 - * Get the contents of a set of CSS files, remap then and concatenate
696 - * them, with newlines in between. Each file is used only once.
697 - *
698 - * @param $styles Array of file names
699 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
700 - */
701 - protected static function concatStyles( $styles ) {
702 - $styles = self::organizeFilesByOption( $styles, 'media', 'all' );
703 - foreach ( $styles as $media => $files ) {
704 - $styles[$media] =
705 - implode( "\n",
706 - array_map(
707 - array( __CLASS__, 'remapStyle' ),
708 - array_unique( (array) $files ) ) );
709 - }
710 - return $styles;
711 - }
712 -
713 - /**
714 - * Remap a relative to $IP. Used as a callback for array_map()
715 - *
716 - * @param $file String: file name
717 - * @return string $IP/$file
718 - */
719 - protected static function remapFilename( $file ) {
720 - global $IP;
721 -
722 - return "$IP/$file";
723 - }
724 -
725 - /**
726 - * Get the contents of a CSS file and run it through CSSMin::remap().
727 - * This wrapper is needed so we can use array_map() in concatStyles()
728 - *
729 - * @param $file String: file name
730 - * @return string Remapped CSS
731 - */
732 - protected static function remapStyle( $file ) {
733 - global $wgScriptPath;
734 - return CSSMin::remap(
735 - file_get_contents( self::remapFilename( $file ) ),
736 - dirname( $file ),
737 - $wgScriptPath . '/' . dirname( $file ),
738 - true
739 - );
740 - }
741 -}
742 -
743 -/**
744 - * Abstraction for resource loader modules which pull from wiki pages
745 - *
746 - * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
747 - * functionality of Title::isValidCssJsSubpage.
748 - */
749 -abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
750 -
751 - /* Protected Members */
752 -
753 - // In-object cache for modified time
754 - protected $modifiedTime = array();
755 -
756 - /* Abstract Protected Methods */
757 -
758 - abstract protected function getPages( ResourceLoaderContext $context );
759 -
760 - /* Protected Methods */
761 -
762 - protected function getContent( $page, $ns ) {
763 - if ( $ns === NS_MEDIAWIKI ) {
764 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
765 - }
766 - if ( $title = Title::newFromText( $page, $ns ) ) {
767 - if ( $title->isValidCssJsSubpage() && $revision = Revision::newFromTitle( $title ) ) {
768 - return $revision->getRawText();
769 - }
770 - }
771 - return null;
772 - }
773 -
774 - /* Methods */
775 -
776 - public function getScript( ResourceLoaderContext $context ) {
777 - $scripts = '';
778 - foreach ( $this->getPages( $context ) as $page => $options ) {
779 - if ( $options['type'] === 'script' ) {
780 - if ( $script = $this->getContent( $page, $options['ns'] ) ) {
781 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
782 - $scripts .= "/*$ns:$page */\n$script\n";
783 - }
784 - }
785 - }
786 - return $scripts;
787 - }
788 -
789 - public function getStyles( ResourceLoaderContext $context ) {
790 -
791 - $styles = array();
792 - foreach ( $this->getPages( $context ) as $page => $options ) {
793 - if ( $options['type'] === 'style' ) {
794 - $media = isset( $options['media'] ) ? $options['media'] : 'all';
795 - if ( $style = $this->getContent( $page, $options['ns'] ) ) {
796 - if ( !isset( $styles[$media] ) ) {
797 - $styles[$media] = '';
798 - }
799 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
800 - $styles[$media] .= "/* $ns:$page */\n$style\n";
801 - }
802 - }
803 - }
804 - return $styles;
805 - }
806 -
807 - public function getModifiedTime( ResourceLoaderContext $context ) {
808 - $hash = $context->getHash();
809 - if ( isset( $this->modifiedTime[$hash] ) ) {
810 - return $this->modifiedTime[$hash];
811 - }
812 -
813 - $titles = array();
814 - foreach ( $this->getPages( $context ) as $page => $options ) {
815 - $titles[$options['ns']][$page] = true;
816 - }
817 -
818 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
819 -
820 - if ( $titles ) {
821 - $dbr = wfGetDB( DB_SLAVE );
822 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
823 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
824 - __METHOD__ );
825 -
826 - if ( $latest ) {
827 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
828 - }
829 - }
830 -
831 - return $this->modifiedTime[$hash] = $modifiedTime;
832 - }
833 -}
834 -
835 -/**
83627 * Module for site customizations
83728 */
83829 class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
@@ -860,309 +51,3 @@
86152 return 'site';
86253 }
86354 }
864 -
865 -/**
866 - * Module for user customizations
867 - */
868 -class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
869 -
870 - /* Protected Methods */
871 -
872 - protected function getPages( ResourceLoaderContext $context ) {
873 - global $wgAllowUserCss;
874 -
875 - if ( $context->getUser() && $wgAllowUserCss ) {
876 - $username = $context->getUser();
877 - return array(
878 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
879 - "$username/" . $context->getSkin() . '.js' => array( 'ns' => NS_USER, 'type' => 'script' ),
880 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
881 - "$username/" . $context->getSkin() . '.css' => array( 'ns' => NS_USER, 'type' => 'style' ),
882 - );
883 - }
884 - return array();
885 - }
886 -
887 - /* Methods */
888 -
889 - public function getGroup() {
890 - return 'user';
891 - }
892 -}
893 -
894 -/**
895 - * Module for user preference customizations
896 - */
897 -class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
898 -
899 - /* Protected Members */
900 -
901 - protected $modifiedTime = array();
902 -
903 - /* Methods */
904 -
905 - public function getModifiedTime( ResourceLoaderContext $context ) {
906 - $hash = $context->getHash();
907 - if ( isset( $this->modifiedTime[$hash] ) ) {
908 - return $this->modifiedTime[$hash];
909 - }
910 -
911 - global $wgUser;
912 -
913 - if ( $context->getUser() === $wgUser->getName() ) {
914 - return $this->modifiedTime[$hash] = $wgUser->getTouched();
915 - } else {
916 - return 1;
917 - }
918 - }
919 -
920 - /**
921 - * Fetch the context's user options, or if it doesn't match current user,
922 - * the default options.
923 - *
924 - * @param $context ResourceLoaderContext
925 - * @return array
926 - */
927 - protected function contextUserOptions( ResourceLoaderContext $context ) {
928 - global $wgUser;
929 -
930 - // Verify identity -- this is a private module
931 - if ( $context->getUser() === $wgUser->getName() ) {
932 - return $wgUser->getOptions();
933 - } else {
934 - return User::getDefaultOptions();
935 - }
936 - }
937 -
938 - public function getScript( ResourceLoaderContext $context ) {
939 - $encOptions = FormatJson::encode( $this->contextUserOptions( $context ) );
940 - return "mediaWiki.user.options.set( $encOptions );";
941 - }
942 -
943 - public function getStyles( ResourceLoaderContext $context ) {
944 - global $wgAllowUserCssPrefs;
945 -
946 - if ( $wgAllowUserCssPrefs ) {
947 - $options = $this->contextUserOptions( $context );
948 -
949 - // Build CSS rules
950 - $rules = array();
951 - if ( $options['underline'] < 2 ) {
952 - $rules[] = "a { text-decoration: " . ( $options['underline'] ? 'underline' : 'none' ) . "; }";
953 - }
954 - if ( $options['highlightbroken'] ) {
955 - $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
956 - } else {
957 - $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
958 - $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
959 - $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
960 - }
961 - if ( $options['justify'] ) {
962 - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
963 - }
964 - if ( !$options['showtoc'] ) {
965 - $rules[] = "#toc { display: none; }\n";
966 - }
967 - if ( !$options['editsection'] ) {
968 - $rules[] = ".editsection { display: none; }\n";
969 - }
970 - if ( $options['editfont'] !== 'default' ) {
971 - $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
972 - }
973 - return array( 'all' => implode( "\n", $rules ) );
974 - }
975 - return array();
976 - }
977 -
978 - public function getFlip( $context ) {
979 - global $wgContLang;
980 -
981 - return $wgContLang->getDir() !== $context->getDirection();
982 - }
983 -
984 - public function getGroup() {
985 - return 'private';
986 - }
987 -}
988 -
989 -class ResourceLoaderStartUpModule extends ResourceLoaderModule {
990 - /* Protected Members */
991 -
992 - protected $modifiedTime = array();
993 -
994 - /* Protected Methods */
995 -
996 - protected function getConfig( $context ) {
997 - global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
998 - $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgBreakFrames,
999 - $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
1000 - $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
1001 - $wgSitename, $wgFileExtensions;
1002 -
1003 - // Pre-process information
1004 - $separatorTransTable = $wgContLang->separatorTransformTable();
1005 - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
1006 - $compactSeparatorTransTable = array(
1007 - implode( "\t", array_keys( $separatorTransTable ) ),
1008 - implode( "\t", $separatorTransTable ),
1009 - );
1010 - $digitTransTable = $wgContLang->digitTransformTable();
1011 - $digitTransTable = $digitTransTable ? $digitTransTable : array();
1012 - $compactDigitTransTable = array(
1013 - implode( "\t", array_keys( $digitTransTable ) ),
1014 - implode( "\t", $digitTransTable ),
1015 - );
1016 - $mainPage = Title::newMainPage();
1017 -
1018 - // Build list of variables
1019 - $vars = array(
1020 - 'wgLoadScript' => $wgLoadScript,
1021 - 'debug' => $context->getDebug(),
1022 - 'skin' => $context->getSkin(),
1023 - 'stylepath' => $wgStylePath,
1024 - 'wgUrlProtocols' => wfUrlProtocols(),
1025 - 'wgArticlePath' => $wgArticlePath,
1026 - 'wgScriptPath' => $wgScriptPath,
1027 - 'wgScriptExtension' => $wgScriptExtension,
1028 - 'wgScript' => $wgScript,
1029 - 'wgVariantArticlePath' => $wgVariantArticlePath,
1030 - 'wgActionPaths' => $wgActionPaths,
1031 - 'wgServer' => $wgServer,
1032 - 'wgUserLanguage' => $context->getLanguage(),
1033 - 'wgContentLanguage' => $wgContLang->getCode(),
1034 - 'wgBreakFrames' => $wgBreakFrames,
1035 - 'wgVersion' => $wgVersion,
1036 - 'wgEnableAPI' => $wgEnableAPI,
1037 - 'wgEnableWriteAPI' => $wgEnableWriteAPI,
1038 - 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
1039 - 'wgDigitTransformTable' => $compactDigitTransTable,
1040 - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
1041 - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
1042 - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
1043 - 'wgSiteName' => $wgSitename,
1044 - 'wgFileExtensions' => $wgFileExtensions,
1045 - 'wgDBname' => $wgDBname,
1046 - );
1047 - if ( $wgContLang->hasVariants() ) {
1048 - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
1049 - }
1050 - if ( $wgUseAjax && $wgEnableMWSuggest ) {
1051 - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
1052 - }
1053 -
1054 - return $vars;
1055 - }
1056 -
1057 - /**
1058 - * Gets registration code for all modules
1059 - *
1060 - * @param $context ResourceLoaderContext object
1061 - * @return String: JavaScript code for registering all modules with the client loader
1062 - */
1063 - public static function getModuleRegistrations( ResourceLoaderContext $context ) {
1064 - global $wgCacheEpoch;
1065 - wfProfileIn( __METHOD__ );
1066 -
1067 - $out = '';
1068 - $registrations = array();
1069 - foreach ( $context->getResourceLoader()->getModules() as $name => $module ) {
1070 - // Support module loader scripts
1071 - if ( ( $loader = $module->getLoaderScript() ) !== false ) {
1072 - $deps = $module->getDependencies();
1073 - $group = $module->getGroup();
1074 - $version = wfTimestamp( TS_ISO_8601_BASIC, round( $module->getModifiedTime( $context ), -2 ) );
1075 - $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
1076 - }
1077 - // Automatically register module
1078 - else {
1079 - $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
1080 - // Modules without dependencies or a group pass two arguments (name, timestamp) to
1081 - // mediaWiki.loader.register()
1082 - if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
1083 - $registrations[] = array( $name, $mtime );
1084 - }
1085 - // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies)
1086 - // to mediaWiki.loader.register()
1087 - else if ( $module->getGroup() === null ) {
1088 - $registrations[] = array(
1089 - $name, $mtime, $module->getDependencies() );
1090 - }
1091 - // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
1092 - // to mediaWiki.loader.register()
1093 - else {
1094 - $registrations[] = array(
1095 - $name, $mtime, $module->getDependencies(), $module->getGroup() );
1096 - }
1097 - }
1098 - }
1099 - $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
1100 -
1101 - wfProfileOut( __METHOD__ );
1102 - return $out;
1103 - }
1104 -
1105 - /* Methods */
1106 -
1107 - public function getScript( ResourceLoaderContext $context ) {
1108 - global $IP, $wgLoadScript;
1109 -
1110 - $out = file_get_contents( "$IP/resources/startup.js" );
1111 - if ( $context->getOnly() === 'scripts' ) {
1112 - // Build load query for jquery and mediawiki modules
1113 - $query = array(
1114 - 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
1115 - 'only' => 'scripts',
1116 - 'lang' => $context->getLanguage(),
1117 - 'skin' => $context->getSkin(),
1118 - 'debug' => $context->getDebug() ? 'true' : 'false',
1119 - 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
1120 - $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
1121 - $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
1122 - ), -2 ) )
1123 - );
1124 - // Ensure uniform query order
1125 - ksort( $query );
1126 -
1127 - // Startup function
1128 - $configuration = FormatJson::encode( $this->getConfig( $context ) );
1129 - $registrations = self::getModuleRegistrations( $context );
1130 - $out .= "var startUp = function() {\n\t$registrations\n\tmediaWiki.config.set( $configuration );\n};";
1131 -
1132 - // Conditional script injection
1133 - $scriptTag = Xml::escapeJsString( Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) );
1134 - $out .= "if ( isCompatible() ) {\n\tdocument.write( '$scriptTag' );\n}\ndelete isCompatible;";
1135 - }
1136 -
1137 - return $out;
1138 - }
1139 -
1140 - public function getModifiedTime( ResourceLoaderContext $context ) {
1141 - global $IP, $wgCacheEpoch;
1142 -
1143 - $hash = $context->getHash();
1144 - if ( isset( $this->modifiedTime[$hash] ) ) {
1145 - return $this->modifiedTime[$hash];
1146 - }
1147 - $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
1148 -
1149 - // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
1150 - // before making changes to this code!
1151 - $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
1152 - foreach ( $context->getResourceLoader()->getModules() as $module ) {
1153 - $time = max( $time, $module->getModifiedTime( $context ) );
1154 - }
1155 - return $this->modifiedTime[$hash] = $time;
1156 - }
1157 -
1158 - public function getFlip( $context ) {
1159 - global $wgContLang;
1160 -
1161 - return $wgContLang->getDir() !== $context->getDirection();
1162 - }
1163 -
1164 - /* Methods */
1165 -
1166 - public function getGroup() {
1167 - return 'startup';
1168 - }
1169 -}
Index: trunk/phase3/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -23,874 +23,6 @@
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
2626 /**
27 - * Abstraction for resource loader modules, with name registration and maxage functionality.
28 - */
29 -abstract class ResourceLoaderModule {
30 -
31 - /* Protected Members */
32 -
33 - protected $name = null;
34 -
35 - // In-object cache for file dependencies
36 - protected $fileDeps = array();
37 - // In-object cache for message blob mtime
38 - protected $msgBlobMtime = array();
39 -
40 - /* Methods */
41 -
42 - /**
43 - * Get this module's name. This is set when the module is registered
44 - * with ResourceLoader::register()
45 - *
46 - * @return Mixed: name (string) or null if no name was set
47 - */
48 - public function getName() {
49 - return $this->name;
50 - }
51 -
52 - /**
53 - * Set this module's name. This is called by ResourceLodaer::register()
54 - * when registering the module. Other code should not call this.
55 - *
56 - * @param $name String: name
57 - */
58 - public function setName( $name ) {
59 - $this->name = $name;
60 - }
61 -
62 - /**
63 - * Get whether CSS for this module should be flipped
64 - */
65 - public function getFlip( $context ) {
66 - return $context->getDirection() === 'rtl';
67 - }
68 -
69 - /**
70 - * Get all JS for this module for a given language and skin.
71 - * Includes all relevant JS except loader scripts.
72 - *
73 - * @param $context ResourceLoaderContext object
74 - * @return String: JS
75 - */
76 - public function getScript( ResourceLoaderContext $context ) {
77 - // Stub, override expected
78 - return '';
79 - }
80 -
81 - /**
82 - * Get all CSS for this module for a given skin.
83 - *
84 - * @param $context ResourceLoaderContext object
85 - * @return array: strings of CSS keyed by media type
86 - */
87 - public function getStyles( ResourceLoaderContext $context ) {
88 - // Stub, override expected
89 - return '';
90 - }
91 -
92 - /**
93 - * Get the messages needed for this module.
94 - *
95 - * To get a JSON blob with messages, use MessageBlobStore::get()
96 - *
97 - * @return array of message keys. Keys may occur more than once
98 - */
99 - public function getMessages() {
100 - // Stub, override expected
101 - return array();
102 - }
103 -
104 - /**
105 - * Get the group this module is in.
106 - *
107 - * @return string of group name
108 - */
109 - public function getGroup() {
110 - // Stub, override expected
111 - return null;
112 - }
113 -
114 - /**
115 - * Get the loader JS for this module, if set.
116 - *
117 - * @return Mixed: loader JS (string) or false if no custom loader set
118 - */
119 - public function getLoaderScript() {
120 - // Stub, override expected
121 - return false;
122 - }
123 -
124 - /**
125 - * Get a list of modules this module depends on.
126 - *
127 - * Dependency information is taken into account when loading a module
128 - * on the client side. When adding a module on the server side,
129 - * dependency information is NOT taken into account and YOU are
130 - * responsible for adding dependent modules as well. If you don't do
131 - * this, the client side loader will send a second request back to the
132 - * server to fetch the missing modules, which kind of defeats the
133 - * purpose of the resource loader.
134 - *
135 - * To add dependencies dynamically on the client side, use a custom
136 - * loader script, see getLoaderScript()
137 - * @return Array of module names (strings)
138 - */
139 - public function getDependencies() {
140 - // Stub, override expected
141 - return array();
142 - }
143 -
144 - /**
145 - * Get the files this module depends on indirectly for a given skin.
146 - * Currently these are only image files referenced by the module's CSS.
147 - *
148 - * @param $skin String: skin name
149 - * @return array of files
150 - */
151 - public function getFileDependencies( $skin ) {
152 - // Try in-object cache first
153 - if ( isset( $this->fileDeps[$skin] ) ) {
154 - return $this->fileDeps[$skin];
155 - }
156 -
157 - $dbr = wfGetDB( DB_SLAVE );
158 - $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
159 - 'md_module' => $this->getName(),
160 - 'md_skin' => $skin,
161 - ), __METHOD__
162 - );
163 - if ( !is_null( $deps ) ) {
164 - return $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
165 - }
166 - return $this->fileDeps[$skin] = array();
167 - }
168 -
169 - /**
170 - * Set preloaded file dependency information. Used so we can load this
171 - * information for all modules at once.
172 - * @param $skin string Skin name
173 - * @param $deps array Array of file names
174 - */
175 - public function setFileDependencies( $skin, $deps ) {
176 - $this->fileDeps[$skin] = $deps;
177 - }
178 -
179 - /**
180 - * Get the last modification timestamp of the message blob for this
181 - * module in a given language.
182 - * @param $lang string Language code
183 - * @return int UNIX timestamp, or 0 if no blob found
184 - */
185 - public function getMsgBlobMtime( $lang ) {
186 - if ( !count( $this->getMessages() ) )
187 - return 0;
188 -
189 - $dbr = wfGetDB( DB_SLAVE );
190 - $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
191 - 'mr_resource' => $this->getName(),
192 - 'mr_lang' => $lang
193 - ), __METHOD__
194 - );
195 - $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
196 - return $this->msgBlobMtime[$lang];
197 - }
198 -
199 - /**
200 - * Set a preloaded message blob last modification timestamp. Used so we
201 - * can load this information for all modules at once.
202 - * @param $lang string Language code
203 - * @param $mtime int UNIX timestamp or 0 if there is no such blob
204 - */
205 - public function setMsgBlobMtime( $lang, $mtime ) {
206 - $this->msgBlobMtime[$lang] = $mtime;
207 - }
208 -
209 - /* Abstract Methods */
210 -
211 - /**
212 - * Get this module's last modification timestamp for a given
213 - * combination of language, skin and debug mode flag. This is typically
214 - * the highest of each of the relevant components' modification
215 - * timestamps. Whenever anything happens that changes the module's
216 - * contents for these parameters, the mtime should increase.
217 - *
218 - * @param $context ResourceLoaderContext object
219 - * @return int UNIX timestamp
220 - */
221 - public function getModifiedTime( ResourceLoaderContext $context ) {
222 - // 0 would mean now
223 - return 1;
224 - }
225 -}
226 -
227 -/**
228 - * Module based on local JS/CSS files. This is the most common type of module.
229 - */
230 -class ResourceLoaderFileModule extends ResourceLoaderModule {
231 - /* Protected Members */
232 -
233 - protected $scripts = array();
234 - protected $styles = array();
235 - protected $messages = array();
236 - protected $group;
237 - protected $dependencies = array();
238 - protected $debugScripts = array();
239 - protected $languageScripts = array();
240 - protected $skinScripts = array();
241 - protected $skinStyles = array();
242 - protected $loaders = array();
243 - protected $parameters = array();
244 -
245 - // In-object cache for file dependencies
246 - protected $fileDeps = array();
247 - // In-object cache for mtime
248 - protected $modifiedTime = array();
249 -
250 - /* Methods */
251 -
252 - /**
253 - * Construct a new module from an options array.
254 - *
255 - * @param $options array Options array. If empty, an empty module will be constructed
256 - *
257 - * $options format:
258 - * array(
259 - * // Required module options (mutually exclusive)
260 - * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ),
261 - *
262 - * // Optional module options
263 - * 'languageScripts' => array(
264 - * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... )
265 - * ...
266 - * ),
267 - * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ),
268 - * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ),
269 - *
270 - * // Non-raw module options
271 - * 'dependencies' => 'module' | array( 'module1', 'module2' ... )
272 - * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ),
273 - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), |
274 - * array( 'dir/file1.css' => array( 'media' => 'print' ) ),
275 - * 'skinStyles' => array(
276 - * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) |
277 - * array( 'dir/file1.css' => array( 'media' => 'print' )
278 - * ...
279 - * ),
280 - * 'messages' => array( 'message1', 'message2' ... ),
281 - * 'group' => 'stuff',
282 - * )
283 - *
284 - * @param $basePath String: base path to prepend to all paths in $options
285 - */
286 - public function __construct( $options = array(), $basePath = null ) {
287 - foreach ( $options as $option => $value ) {
288 - switch ( $option ) {
289 - case 'scripts':
290 - case 'debugScripts':
291 - case 'languageScripts':
292 - case 'skinScripts':
293 - case 'loaders':
294 - $this->{$option} = (array)$value;
295 - // Automatically prefix script paths
296 - if ( is_string( $basePath ) ) {
297 - foreach ( $this->{$option} as $key => $value ) {
298 - $this->{$option}[$key] = $basePath . $value;
299 - }
300 - }
301 - break;
302 - case 'styles':
303 - case 'skinStyles':
304 - $this->{$option} = (array)$value;
305 - // Automatically prefix style paths
306 - if ( is_string( $basePath ) ) {
307 - foreach ( $this->{$option} as $key => $value ) {
308 - if ( is_array( $value ) ) {
309 - $this->{$option}[$basePath . $key] = $value;
310 - unset( $this->{$option}[$key] );
311 - } else {
312 - $this->{$option}[$key] = $basePath . $value;
313 - }
314 - }
315 - }
316 - break;
317 - case 'dependencies':
318 - case 'messages':
319 - $this->{$option} = (array)$value;
320 - break;
321 - case 'group':
322 - $this->group = (string)$value;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - /**
329 - * Add script files to this module. In order to be valid, a module
330 - * must contain at least one script file.
331 - *
332 - * @param $scripts Mixed: path to script file (string) or array of paths
333 - */
334 - public function addScripts( $scripts ) {
335 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
336 - }
337 -
338 - /**
339 - * Add style (CSS) files to this module.
340 - *
341 - * @param $styles Mixed: path to CSS file (string) or array of paths
342 - */
343 - public function addStyles( $styles ) {
344 - $this->styles = array_merge( $this->styles, (array)$styles );
345 - }
346 -
347 - /**
348 - * Add messages to this module.
349 - *
350 - * @param $messages Mixed: message key (string) or array of message keys
351 - */
352 - public function addMessages( $messages ) {
353 - $this->messages = array_merge( $this->messages, (array)$messages );
354 - }
355 -
356 - /**
357 - * Sets the group of this module.
358 - *
359 - * @param $group string group name
360 - */
361 - public function setGroup( $group ) {
362 - $this->group = $group;
363 - }
364 -
365 - /**
366 - * Add dependencies. Dependency information is taken into account when
367 - * loading a module on the client side. When adding a module on the
368 - * server side, dependency information is NOT taken into account and
369 - * YOU are responsible for adding dependent modules as well. If you
370 - * don't do this, the client side loader will send a second request
371 - * back to the server to fetch the missing modules, which kind of
372 - * defeats the point of using the resource loader in the first place.
373 - *
374 - * To add dependencies dynamically on the client side, use a custom
375 - * loader (see addLoaders())
376 - *
377 - * @param $dependencies Mixed: module name (string) or array of module names
378 - */
379 - public function addDependencies( $dependencies ) {
380 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
381 - }
382 -
383 - /**
384 - * Add debug scripts to the module. These scripts are only included
385 - * in debug mode.
386 - *
387 - * @param $scripts Mixed: path to script file (string) or array of paths
388 - */
389 - public function addDebugScripts( $scripts ) {
390 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
391 - }
392 -
393 - /**
394 - * Add language-specific scripts. These scripts are only included for
395 - * a given language.
396 - *
397 - * @param $lang String: language code
398 - * @param $scripts Mixed: path to script file (string) or array of paths
399 - */
400 - public function addLanguageScripts( $lang, $scripts ) {
401 - $this->languageScripts = array_merge_recursive(
402 - $this->languageScripts,
403 - array( $lang => $scripts )
404 - );
405 - }
406 -
407 - /**
408 - * Add skin-specific scripts. These scripts are only included for
409 - * a given skin.
410 - *
411 - * @param $skin String: skin name, or 'default'
412 - * @param $scripts Mixed: path to script file (string) or array of paths
413 - */
414 - public function addSkinScripts( $skin, $scripts ) {
415 - $this->skinScripts = array_merge_recursive(
416 - $this->skinScripts,
417 - array( $skin => $scripts )
418 - );
419 - }
420 -
421 - /**
422 - * Add skin-specific CSS. These CSS files are only included for a
423 - * given skin. If there are no skin-specific CSS files for a skin,
424 - * the files defined for 'default' will be used, if any.
425 - *
426 - * @param $skin String: skin name, or 'default'
427 - * @param $scripts Mixed: path to CSS file (string) or array of paths
428 - */
429 - public function addSkinStyles( $skin, $scripts ) {
430 - $this->skinStyles = array_merge_recursive(
431 - $this->skinStyles,
432 - array( $skin => $scripts )
433 - );
434 - }
435 -
436 - /**
437 - * Add loader scripts. These scripts are loaded on every page and are
438 - * responsible for registering this module using
439 - * mediaWiki.loader.register(). If there are no loader scripts defined,
440 - * the resource loader will register the module itself.
441 - *
442 - * Loader scripts are used to determine a module's dependencies
443 - * dynamically on the client side (e.g. based on browser type/version).
444 - * Note that loader scripts are included on every page, so they should
445 - * be lightweight and use mediaWiki.loader.register()'s callback
446 - * feature to defer dependency calculation.
447 - *
448 - * @param $scripts Mixed: path to script file (string) or array of paths
449 - */
450 - public function addLoaders( $scripts ) {
451 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
452 - }
453 -
454 - public function getScript( ResourceLoaderContext $context ) {
455 - $retval = $this->getPrimaryScript() . "\n" .
456 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
457 - $this->getSkinScript( $context->getSkin() );
458 -
459 - if ( $context->getDebug() ) {
460 - $retval .= $this->getDebugScript();
461 - }
462 -
463 - return $retval;
464 - }
465 -
466 - public function getStyles( ResourceLoaderContext $context ) {
467 - $styles = array();
468 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
469 - if ( !isset( $styles[$media] ) ) {
470 - $styles[$media] = '';
471 - }
472 - $styles[$media] .= $style;
473 - }
474 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
475 - if ( !isset( $styles[$media] ) ) {
476 - $styles[$media] = '';
477 - }
478 - $styles[$media] .= $style;
479 - }
480 -
481 - // Collect referenced files
482 - $files = array();
483 - foreach ( $styles as $style ) {
484 - // Extract and store the list of referenced files
485 - $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
486 - }
487 -
488 - // Only store if modified
489 - if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
490 - $encFiles = FormatJson::encode( $files );
491 - $dbw = wfGetDB( DB_MASTER );
492 - $dbw->replace( 'module_deps',
493 - array( array( 'md_module', 'md_skin' ) ), array(
494 - 'md_module' => $this->getName(),
495 - 'md_skin' => $context->getSkin(),
496 - 'md_deps' => $encFiles,
497 - )
498 - );
499 - }
500 -
501 - return $styles;
502 - }
503 -
504 - public function getMessages() {
505 - return $this->messages;
506 - }
507 -
508 - public function getGroup() {
509 - return $this->group;
510 - }
511 -
512 - public function getDependencies() {
513 - return $this->dependencies;
514 - }
515 -
516 - public function getLoaderScript() {
517 - if ( count( $this->loaders ) == 0 ) {
518 - return false;
519 - }
520 -
521 - return self::concatScripts( $this->loaders );
522 - }
523 -
524 - /**
525 - * Get the last modified timestamp of this module, which is calculated
526 - * as the highest last modified timestamp of its constituent files and
527 - * the files it depends on (see getFileDependencies()). Only files
528 - * relevant to the given language and skin are taken into account, and
529 - * files only relevant in debug mode are not taken into account when
530 - * debug mode is off.
531 - *
532 - * @param $context ResourceLoaderContext object
533 - * @return Integer: UNIX timestamp
534 - */
535 - public function getModifiedTime( ResourceLoaderContext $context ) {
536 - if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
537 - return $this->modifiedTime[$context->getHash()];
538 - }
539 - wfProfileIn( __METHOD__ );
540 -
541 - // Sort of nasty way we can get a flat list of files depended on by all styles
542 - $styles = array();
543 - foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
544 - $styles = array_merge( $styles, $styleFiles );
545 - }
546 - $skinFiles = (array) self::getSkinFiles(
547 - $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' )
548 - );
549 - foreach ( $skinFiles as $styleFiles ) {
550 - $styles = array_merge( $styles, $styleFiles );
551 - }
552 -
553 - // Final merge, this should result in a master list of dependent files
554 - $files = array_merge(
555 - $this->scripts,
556 - $styles,
557 - $context->getDebug() ? $this->debugScripts : array(),
558 - isset( $this->languageScripts[$context->getLanguage()] ) ?
559 - (array) $this->languageScripts[$context->getLanguage()] : array(),
560 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
561 - $this->loaders,
562 - $this->getFileDependencies( $context->getSkin() )
563 - );
564 -
565 - wfProfileIn( __METHOD__.'-filemtime' );
566 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
567 - wfProfileOut( __METHOD__.'-filemtime' );
568 - $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
569 - wfProfileOut( __METHOD__ );
570 - return $this->modifiedTime[$context->getHash()];
571 - }
572 -
573 - /* Protected Members */
574 -
575 - /**
576 - * Get the primary JS for this module. This is pulled from the
577 - * script files added through addScripts()
578 - *
579 - * @return String: JS
580 - */
581 - protected function getPrimaryScript() {
582 - return self::concatScripts( $this->scripts );
583 - }
584 -
585 - /**
586 - * Get the primary CSS for this module. This is pulled from the CSS
587 - * files added through addStyles()
588 - *
589 - * @return Array
590 - */
591 - protected function getPrimaryStyles() {
592 - return self::concatStyles( $this->styles );
593 - }
594 -
595 - /**
596 - * Get the debug JS for this module. This is pulled from the script
597 - * files added through addDebugScripts()
598 - *
599 - * @return String: JS
600 - */
601 - protected function getDebugScript() {
602 - return self::concatScripts( $this->debugScripts );
603 - }
604 -
605 - /**
606 - * Get the language-specific JS for a given language. This is pulled
607 - * from the language-specific script files added through addLanguageScripts()
608 - *
609 - * @return String: JS
610 - */
611 - protected function getLanguageScript( $lang ) {
612 - if ( !isset( $this->languageScripts[$lang] ) ) {
613 - return '';
614 - }
615 - return self::concatScripts( $this->languageScripts[$lang] );
616 - }
617 -
618 - /**
619 - * Get the skin-specific JS for a given skin. This is pulled from the
620 - * skin-specific JS files added through addSkinScripts()
621 - *
622 - * @return String: JS
623 - */
624 - protected function getSkinScript( $skin ) {
625 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
626 - }
627 -
628 - /**
629 - * Get the skin-specific CSS for a given skin. This is pulled from the
630 - * skin-specific CSS files added through addSkinStyles()
631 - *
632 - * @return Array: list of CSS strings keyed by media type
633 - */
634 - protected function getSkinStyles( $skin ) {
635 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
636 - }
637 -
638 - /**
639 - * Helper function to get skin-specific data from an array.
640 - *
641 - * @param $skin String: skin name
642 - * @param $map Array: map of skin names to arrays
643 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
644 - */
645 - protected static function getSkinFiles( $skin, $map ) {
646 - $retval = array();
647 -
648 - if ( isset( $map[$skin] ) && $map[$skin] ) {
649 - $retval = $map[$skin];
650 - } else if ( isset( $map['default'] ) ) {
651 - $retval = $map['default'];
652 - }
653 -
654 - return $retval;
655 - }
656 -
657 - /**
658 - * Get the contents of a set of files and concatenate them, with
659 - * newlines in between. Each file is used only once.
660 - *
661 - * @param $files Array of file names
662 - * @return String: concatenated contents of $files
663 - */
664 - protected static function concatScripts( $files ) {
665 - return implode( "\n",
666 - array_map(
667 - 'file_get_contents',
668 - array_map(
669 - array( __CLASS__, 'remapFilename' ),
670 - array_unique( (array) $files ) ) ) );
671 - }
672 -
673 - protected static function organizeFilesByOption( $files, $option, $default ) {
674 - $organizedFiles = array();
675 - foreach ( (array) $files as $key => $value ) {
676 - if ( is_int( $key ) ) {
677 - // File name as the value
678 - if ( !isset( $organizedFiles[$default] ) ) {
679 - $organizedFiles[$default] = array();
680 - }
681 - $organizedFiles[$default][] = $value;
682 - } else if ( is_array( $value ) ) {
683 - // File name as the key, options array as the value
684 - $media = isset( $value[$option] ) ? $value[$option] : $default;
685 - if ( !isset( $organizedFiles[$media] ) ) {
686 - $organizedFiles[$media] = array();
687 - }
688 - $organizedFiles[$media][] = $key;
689 - }
690 - }
691 - return $organizedFiles;
692 - }
693 -
694 - /**
695 - * Get the contents of a set of CSS files, remap then and concatenate
696 - * them, with newlines in between. Each file is used only once.
697 - *
698 - * @param $styles Array of file names
699 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
700 - */
701 - protected static function concatStyles( $styles ) {
702 - $styles = self::organizeFilesByOption( $styles, 'media', 'all' );
703 - foreach ( $styles as $media => $files ) {
704 - $styles[$media] =
705 - implode( "\n",
706 - array_map(
707 - array( __CLASS__, 'remapStyle' ),
708 - array_unique( (array) $files ) ) );
709 - }
710 - return $styles;
711 - }
712 -
713 - /**
714 - * Remap a relative to $IP. Used as a callback for array_map()
715 - *
716 - * @param $file String: file name
717 - * @return string $IP/$file
718 - */
719 - protected static function remapFilename( $file ) {
720 - global $IP;
721 -
722 - return "$IP/$file";
723 - }
724 -
725 - /**
726 - * Get the contents of a CSS file and run it through CSSMin::remap().
727 - * This wrapper is needed so we can use array_map() in concatStyles()
728 - *
729 - * @param $file String: file name
730 - * @return string Remapped CSS
731 - */
732 - protected static function remapStyle( $file ) {
733 - global $wgScriptPath;
734 - return CSSMin::remap(
735 - file_get_contents( self::remapFilename( $file ) ),
736 - dirname( $file ),
737 - $wgScriptPath . '/' . dirname( $file ),
738 - true
739 - );
740 - }
741 -}
742 -
743 -/**
744 - * Abstraction for resource loader modules which pull from wiki pages
745 - *
746 - * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
747 - * functionality of Title::isValidCssJsSubpage.
748 - */
749 -abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
750 -
751 - /* Protected Members */
752 -
753 - // In-object cache for modified time
754 - protected $modifiedTime = array();
755 -
756 - /* Abstract Protected Methods */
757 -
758 - abstract protected function getPages( ResourceLoaderContext $context );
759 -
760 - /* Protected Methods */
761 -
762 - protected function getContent( $page, $ns ) {
763 - if ( $ns === NS_MEDIAWIKI ) {
764 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
765 - }
766 - if ( $title = Title::newFromText( $page, $ns ) ) {
767 - if ( $title->isValidCssJsSubpage() && $revision = Revision::newFromTitle( $title ) ) {
768 - return $revision->getRawText();
769 - }
770 - }
771 - return null;
772 - }
773 -
774 - /* Methods */
775 -
776 - public function getScript( ResourceLoaderContext $context ) {
777 - $scripts = '';
778 - foreach ( $this->getPages( $context ) as $page => $options ) {
779 - if ( $options['type'] === 'script' ) {
780 - if ( $script = $this->getContent( $page, $options['ns'] ) ) {
781 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
782 - $scripts .= "/*$ns:$page */\n$script\n";
783 - }
784 - }
785 - }
786 - return $scripts;
787 - }
788 -
789 - public function getStyles( ResourceLoaderContext $context ) {
790 -
791 - $styles = array();
792 - foreach ( $this->getPages( $context ) as $page => $options ) {
793 - if ( $options['type'] === 'style' ) {
794 - $media = isset( $options['media'] ) ? $options['media'] : 'all';
795 - if ( $style = $this->getContent( $page, $options['ns'] ) ) {
796 - if ( !isset( $styles[$media] ) ) {
797 - $styles[$media] = '';
798 - }
799 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
800 - $styles[$media] .= "/* $ns:$page */\n$style\n";
801 - }
802 - }
803 - }
804 - return $styles;
805 - }
806 -
807 - public function getModifiedTime( ResourceLoaderContext $context ) {
808 - $hash = $context->getHash();
809 - if ( isset( $this->modifiedTime[$hash] ) ) {
810 - return $this->modifiedTime[$hash];
811 - }
812 -
813 - $titles = array();
814 - foreach ( $this->getPages( $context ) as $page => $options ) {
815 - $titles[$options['ns']][$page] = true;
816 - }
817 -
818 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
819 -
820 - if ( $titles ) {
821 - $dbr = wfGetDB( DB_SLAVE );
822 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
823 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
824 - __METHOD__ );
825 -
826 - if ( $latest ) {
827 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
828 - }
829 - }
830 -
831 - return $this->modifiedTime[$hash] = $modifiedTime;
832 - }
833 -}
834 -
835 -/**
836 - * Module for site customizations
837 - */
838 -class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
839 -
840 - /* Protected Methods */
841 -
842 - protected function getPages( ResourceLoaderContext $context ) {
843 - global $wgHandheldStyle;
844 -
845 - $pages = array(
846 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
847 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
848 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
849 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
850 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
851 - );
852 - if ( $wgHandheldStyle ) {
853 - $pages['Handheld.css'] = array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'handheld' );
854 - }
855 - return $pages;
856 - }
857 -
858 - /* Methods */
859 -
860 - public function getGroup() {
861 - return 'site';
862 - }
863 -}
864 -
865 -/**
866 - * Module for user customizations
867 - */
868 -class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
869 -
870 - /* Protected Methods */
871 -
872 - protected function getPages( ResourceLoaderContext $context ) {
873 - global $wgAllowUserCss;
874 -
875 - if ( $context->getUser() && $wgAllowUserCss ) {
876 - $username = $context->getUser();
877 - return array(
878 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
879 - "$username/" . $context->getSkin() . '.js' => array( 'ns' => NS_USER, 'type' => 'script' ),
880 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
881 - "$username/" . $context->getSkin() . '.css' => array( 'ns' => NS_USER, 'type' => 'style' ),
882 - );
883 - }
884 - return array();
885 - }
886 -
887 - /* Methods */
888 -
889 - public function getGroup() {
890 - return 'user';
891 - }
892 -}
893 -
894 -/**
89527 * Module for user preference customizations
89628 */
89729 class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
@@ -984,185 +116,3 @@
985117 return 'private';
986118 }
987119 }
988 -
989 -class ResourceLoaderStartUpModule extends ResourceLoaderModule {
990 - /* Protected Members */
991 -
992 - protected $modifiedTime = array();
993 -
994 - /* Protected Methods */
995 -
996 - protected function getConfig( $context ) {
997 - global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
998 - $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgBreakFrames,
999 - $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
1000 - $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
1001 - $wgSitename, $wgFileExtensions;
1002 -
1003 - // Pre-process information
1004 - $separatorTransTable = $wgContLang->separatorTransformTable();
1005 - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
1006 - $compactSeparatorTransTable = array(
1007 - implode( "\t", array_keys( $separatorTransTable ) ),
1008 - implode( "\t", $separatorTransTable ),
1009 - );
1010 - $digitTransTable = $wgContLang->digitTransformTable();
1011 - $digitTransTable = $digitTransTable ? $digitTransTable : array();
1012 - $compactDigitTransTable = array(
1013 - implode( "\t", array_keys( $digitTransTable ) ),
1014 - implode( "\t", $digitTransTable ),
1015 - );
1016 - $mainPage = Title::newMainPage();
1017 -
1018 - // Build list of variables
1019 - $vars = array(
1020 - 'wgLoadScript' => $wgLoadScript,
1021 - 'debug' => $context->getDebug(),
1022 - 'skin' => $context->getSkin(),
1023 - 'stylepath' => $wgStylePath,
1024 - 'wgUrlProtocols' => wfUrlProtocols(),
1025 - 'wgArticlePath' => $wgArticlePath,
1026 - 'wgScriptPath' => $wgScriptPath,
1027 - 'wgScriptExtension' => $wgScriptExtension,
1028 - 'wgScript' => $wgScript,
1029 - 'wgVariantArticlePath' => $wgVariantArticlePath,
1030 - 'wgActionPaths' => $wgActionPaths,
1031 - 'wgServer' => $wgServer,
1032 - 'wgUserLanguage' => $context->getLanguage(),
1033 - 'wgContentLanguage' => $wgContLang->getCode(),
1034 - 'wgBreakFrames' => $wgBreakFrames,
1035 - 'wgVersion' => $wgVersion,
1036 - 'wgEnableAPI' => $wgEnableAPI,
1037 - 'wgEnableWriteAPI' => $wgEnableWriteAPI,
1038 - 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
1039 - 'wgDigitTransformTable' => $compactDigitTransTable,
1040 - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
1041 - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
1042 - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
1043 - 'wgSiteName' => $wgSitename,
1044 - 'wgFileExtensions' => $wgFileExtensions,
1045 - 'wgDBname' => $wgDBname,
1046 - );
1047 - if ( $wgContLang->hasVariants() ) {
1048 - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
1049 - }
1050 - if ( $wgUseAjax && $wgEnableMWSuggest ) {
1051 - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
1052 - }
1053 -
1054 - return $vars;
1055 - }
1056 -
1057 - /**
1058 - * Gets registration code for all modules
1059 - *
1060 - * @param $context ResourceLoaderContext object
1061 - * @return String: JavaScript code for registering all modules with the client loader
1062 - */
1063 - public static function getModuleRegistrations( ResourceLoaderContext $context ) {
1064 - global $wgCacheEpoch;
1065 - wfProfileIn( __METHOD__ );
1066 -
1067 - $out = '';
1068 - $registrations = array();
1069 - foreach ( $context->getResourceLoader()->getModules() as $name => $module ) {
1070 - // Support module loader scripts
1071 - if ( ( $loader = $module->getLoaderScript() ) !== false ) {
1072 - $deps = $module->getDependencies();
1073 - $group = $module->getGroup();
1074 - $version = wfTimestamp( TS_ISO_8601_BASIC, round( $module->getModifiedTime( $context ), -2 ) );
1075 - $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
1076 - }
1077 - // Automatically register module
1078 - else {
1079 - $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
1080 - // Modules without dependencies or a group pass two arguments (name, timestamp) to
1081 - // mediaWiki.loader.register()
1082 - if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
1083 - $registrations[] = array( $name, $mtime );
1084 - }
1085 - // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies)
1086 - // to mediaWiki.loader.register()
1087 - else if ( $module->getGroup() === null ) {
1088 - $registrations[] = array(
1089 - $name, $mtime, $module->getDependencies() );
1090 - }
1091 - // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
1092 - // to mediaWiki.loader.register()
1093 - else {
1094 - $registrations[] = array(
1095 - $name, $mtime, $module->getDependencies(), $module->getGroup() );
1096 - }
1097 - }
1098 - }
1099 - $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
1100 -
1101 - wfProfileOut( __METHOD__ );
1102 - return $out;
1103 - }
1104 -
1105 - /* Methods */
1106 -
1107 - public function getScript( ResourceLoaderContext $context ) {
1108 - global $IP, $wgLoadScript;
1109 -
1110 - $out = file_get_contents( "$IP/resources/startup.js" );
1111 - if ( $context->getOnly() === 'scripts' ) {
1112 - // Build load query for jquery and mediawiki modules
1113 - $query = array(
1114 - 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
1115 - 'only' => 'scripts',
1116 - 'lang' => $context->getLanguage(),
1117 - 'skin' => $context->getSkin(),
1118 - 'debug' => $context->getDebug() ? 'true' : 'false',
1119 - 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
1120 - $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
1121 - $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
1122 - ), -2 ) )
1123 - );
1124 - // Ensure uniform query order
1125 - ksort( $query );
1126 -
1127 - // Startup function
1128 - $configuration = FormatJson::encode( $this->getConfig( $context ) );
1129 - $registrations = self::getModuleRegistrations( $context );
1130 - $out .= "var startUp = function() {\n\t$registrations\n\tmediaWiki.config.set( $configuration );\n};";
1131 -
1132 - // Conditional script injection
1133 - $scriptTag = Xml::escapeJsString( Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) );
1134 - $out .= "if ( isCompatible() ) {\n\tdocument.write( '$scriptTag' );\n}\ndelete isCompatible;";
1135 - }
1136 -
1137 - return $out;
1138 - }
1139 -
1140 - public function getModifiedTime( ResourceLoaderContext $context ) {
1141 - global $IP, $wgCacheEpoch;
1142 -
1143 - $hash = $context->getHash();
1144 - if ( isset( $this->modifiedTime[$hash] ) ) {
1145 - return $this->modifiedTime[$hash];
1146 - }
1147 - $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
1148 -
1149 - // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
1150 - // before making changes to this code!
1151 - $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
1152 - foreach ( $context->getResourceLoader()->getModules() as $module ) {
1153 - $time = max( $time, $module->getModifiedTime( $context ) );
1154 - }
1155 - return $this->modifiedTime[$hash] = $time;
1156 - }
1157 -
1158 - public function getFlip( $context ) {
1159 - global $wgContLang;
1160 -
1161 - return $wgContLang->getDir() !== $context->getDirection();
1162 - }
1163 -
1164 - /* Methods */
1165 -
1166 - public function getGroup() {
1167 - return 'startup';
1168 - }
1169 -}
Index: trunk/phase3/includes/resourceloader/ResourceLoaderUserModule.php
@@ -23,845 +23,6 @@
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
2626 /**
27 - * Abstraction for resource loader modules, with name registration and maxage functionality.
28 - */
29 -abstract class ResourceLoaderModule {
30 -
31 - /* Protected Members */
32 -
33 - protected $name = null;
34 -
35 - // In-object cache for file dependencies
36 - protected $fileDeps = array();
37 - // In-object cache for message blob mtime
38 - protected $msgBlobMtime = array();
39 -
40 - /* Methods */
41 -
42 - /**
43 - * Get this module's name. This is set when the module is registered
44 - * with ResourceLoader::register()
45 - *
46 - * @return Mixed: name (string) or null if no name was set
47 - */
48 - public function getName() {
49 - return $this->name;
50 - }
51 -
52 - /**
53 - * Set this module's name. This is called by ResourceLodaer::register()
54 - * when registering the module. Other code should not call this.
55 - *
56 - * @param $name String: name
57 - */
58 - public function setName( $name ) {
59 - $this->name = $name;
60 - }
61 -
62 - /**
63 - * Get whether CSS for this module should be flipped
64 - */
65 - public function getFlip( $context ) {
66 - return $context->getDirection() === 'rtl';
67 - }
68 -
69 - /**
70 - * Get all JS for this module for a given language and skin.
71 - * Includes all relevant JS except loader scripts.
72 - *
73 - * @param $context ResourceLoaderContext object
74 - * @return String: JS
75 - */
76 - public function getScript( ResourceLoaderContext $context ) {
77 - // Stub, override expected
78 - return '';
79 - }
80 -
81 - /**
82 - * Get all CSS for this module for a given skin.
83 - *
84 - * @param $context ResourceLoaderContext object
85 - * @return array: strings of CSS keyed by media type
86 - */
87 - public function getStyles( ResourceLoaderContext $context ) {
88 - // Stub, override expected
89 - return '';
90 - }
91 -
92 - /**
93 - * Get the messages needed for this module.
94 - *
95 - * To get a JSON blob with messages, use MessageBlobStore::get()
96 - *
97 - * @return array of message keys. Keys may occur more than once
98 - */
99 - public function getMessages() {
100 - // Stub, override expected
101 - return array();
102 - }
103 -
104 - /**
105 - * Get the group this module is in.
106 - *
107 - * @return string of group name
108 - */
109 - public function getGroup() {
110 - // Stub, override expected
111 - return null;
112 - }
113 -
114 - /**
115 - * Get the loader JS for this module, if set.
116 - *
117 - * @return Mixed: loader JS (string) or false if no custom loader set
118 - */
119 - public function getLoaderScript() {
120 - // Stub, override expected
121 - return false;
122 - }
123 -
124 - /**
125 - * Get a list of modules this module depends on.
126 - *
127 - * Dependency information is taken into account when loading a module
128 - * on the client side. When adding a module on the server side,
129 - * dependency information is NOT taken into account and YOU are
130 - * responsible for adding dependent modules as well. If you don't do
131 - * this, the client side loader will send a second request back to the
132 - * server to fetch the missing modules, which kind of defeats the
133 - * purpose of the resource loader.
134 - *
135 - * To add dependencies dynamically on the client side, use a custom
136 - * loader script, see getLoaderScript()
137 - * @return Array of module names (strings)
138 - */
139 - public function getDependencies() {
140 - // Stub, override expected
141 - return array();
142 - }
143 -
144 - /**
145 - * Get the files this module depends on indirectly for a given skin.
146 - * Currently these are only image files referenced by the module's CSS.
147 - *
148 - * @param $skin String: skin name
149 - * @return array of files
150 - */
151 - public function getFileDependencies( $skin ) {
152 - // Try in-object cache first
153 - if ( isset( $this->fileDeps[$skin] ) ) {
154 - return $this->fileDeps[$skin];
155 - }
156 -
157 - $dbr = wfGetDB( DB_SLAVE );
158 - $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
159 - 'md_module' => $this->getName(),
160 - 'md_skin' => $skin,
161 - ), __METHOD__
162 - );
163 - if ( !is_null( $deps ) ) {
164 - return $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
165 - }
166 - return $this->fileDeps[$skin] = array();
167 - }
168 -
169 - /**
170 - * Set preloaded file dependency information. Used so we can load this
171 - * information for all modules at once.
172 - * @param $skin string Skin name
173 - * @param $deps array Array of file names
174 - */
175 - public function setFileDependencies( $skin, $deps ) {
176 - $this->fileDeps[$skin] = $deps;
177 - }
178 -
179 - /**
180 - * Get the last modification timestamp of the message blob for this
181 - * module in a given language.
182 - * @param $lang string Language code
183 - * @return int UNIX timestamp, or 0 if no blob found
184 - */
185 - public function getMsgBlobMtime( $lang ) {
186 - if ( !count( $this->getMessages() ) )
187 - return 0;
188 -
189 - $dbr = wfGetDB( DB_SLAVE );
190 - $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
191 - 'mr_resource' => $this->getName(),
192 - 'mr_lang' => $lang
193 - ), __METHOD__
194 - );
195 - $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
196 - return $this->msgBlobMtime[$lang];
197 - }
198 -
199 - /**
200 - * Set a preloaded message blob last modification timestamp. Used so we
201 - * can load this information for all modules at once.
202 - * @param $lang string Language code
203 - * @param $mtime int UNIX timestamp or 0 if there is no such blob
204 - */
205 - public function setMsgBlobMtime( $lang, $mtime ) {
206 - $this->msgBlobMtime[$lang] = $mtime;
207 - }
208 -
209 - /* Abstract Methods */
210 -
211 - /**
212 - * Get this module's last modification timestamp for a given
213 - * combination of language, skin and debug mode flag. This is typically
214 - * the highest of each of the relevant components' modification
215 - * timestamps. Whenever anything happens that changes the module's
216 - * contents for these parameters, the mtime should increase.
217 - *
218 - * @param $context ResourceLoaderContext object
219 - * @return int UNIX timestamp
220 - */
221 - public function getModifiedTime( ResourceLoaderContext $context ) {
222 - // 0 would mean now
223 - return 1;
224 - }
225 -}
226 -
227 -/**
228 - * Module based on local JS/CSS files. This is the most common type of module.
229 - */
230 -class ResourceLoaderFileModule extends ResourceLoaderModule {
231 - /* Protected Members */
232 -
233 - protected $scripts = array();
234 - protected $styles = array();
235 - protected $messages = array();
236 - protected $group;
237 - protected $dependencies = array();
238 - protected $debugScripts = array();
239 - protected $languageScripts = array();
240 - protected $skinScripts = array();
241 - protected $skinStyles = array();
242 - protected $loaders = array();
243 - protected $parameters = array();
244 -
245 - // In-object cache for file dependencies
246 - protected $fileDeps = array();
247 - // In-object cache for mtime
248 - protected $modifiedTime = array();
249 -
250 - /* Methods */
251 -
252 - /**
253 - * Construct a new module from an options array.
254 - *
255 - * @param $options array Options array. If empty, an empty module will be constructed
256 - *
257 - * $options format:
258 - * array(
259 - * // Required module options (mutually exclusive)
260 - * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ),
261 - *
262 - * // Optional module options
263 - * 'languageScripts' => array(
264 - * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... )
265 - * ...
266 - * ),
267 - * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ),
268 - * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ),
269 - *
270 - * // Non-raw module options
271 - * 'dependencies' => 'module' | array( 'module1', 'module2' ... )
272 - * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ),
273 - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), |
274 - * array( 'dir/file1.css' => array( 'media' => 'print' ) ),
275 - * 'skinStyles' => array(
276 - * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) |
277 - * array( 'dir/file1.css' => array( 'media' => 'print' )
278 - * ...
279 - * ),
280 - * 'messages' => array( 'message1', 'message2' ... ),
281 - * 'group' => 'stuff',
282 - * )
283 - *
284 - * @param $basePath String: base path to prepend to all paths in $options
285 - */
286 - public function __construct( $options = array(), $basePath = null ) {
287 - foreach ( $options as $option => $value ) {
288 - switch ( $option ) {
289 - case 'scripts':
290 - case 'debugScripts':
291 - case 'languageScripts':
292 - case 'skinScripts':
293 - case 'loaders':
294 - $this->{$option} = (array)$value;
295 - // Automatically prefix script paths
296 - if ( is_string( $basePath ) ) {
297 - foreach ( $this->{$option} as $key => $value ) {
298 - $this->{$option}[$key] = $basePath . $value;
299 - }
300 - }
301 - break;
302 - case 'styles':
303 - case 'skinStyles':
304 - $this->{$option} = (array)$value;
305 - // Automatically prefix style paths
306 - if ( is_string( $basePath ) ) {
307 - foreach ( $this->{$option} as $key => $value ) {
308 - if ( is_array( $value ) ) {
309 - $this->{$option}[$basePath . $key] = $value;
310 - unset( $this->{$option}[$key] );
311 - } else {
312 - $this->{$option}[$key] = $basePath . $value;
313 - }
314 - }
315 - }
316 - break;
317 - case 'dependencies':
318 - case 'messages':
319 - $this->{$option} = (array)$value;
320 - break;
321 - case 'group':
322 - $this->group = (string)$value;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - /**
329 - * Add script files to this module. In order to be valid, a module
330 - * must contain at least one script file.
331 - *
332 - * @param $scripts Mixed: path to script file (string) or array of paths
333 - */
334 - public function addScripts( $scripts ) {
335 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
336 - }
337 -
338 - /**
339 - * Add style (CSS) files to this module.
340 - *
341 - * @param $styles Mixed: path to CSS file (string) or array of paths
342 - */
343 - public function addStyles( $styles ) {
344 - $this->styles = array_merge( $this->styles, (array)$styles );
345 - }
346 -
347 - /**
348 - * Add messages to this module.
349 - *
350 - * @param $messages Mixed: message key (string) or array of message keys
351 - */
352 - public function addMessages( $messages ) {
353 - $this->messages = array_merge( $this->messages, (array)$messages );
354 - }
355 -
356 - /**
357 - * Sets the group of this module.
358 - *
359 - * @param $group string group name
360 - */
361 - public function setGroup( $group ) {
362 - $this->group = $group;
363 - }
364 -
365 - /**
366 - * Add dependencies. Dependency information is taken into account when
367 - * loading a module on the client side. When adding a module on the
368 - * server side, dependency information is NOT taken into account and
369 - * YOU are responsible for adding dependent modules as well. If you
370 - * don't do this, the client side loader will send a second request
371 - * back to the server to fetch the missing modules, which kind of
372 - * defeats the point of using the resource loader in the first place.
373 - *
374 - * To add dependencies dynamically on the client side, use a custom
375 - * loader (see addLoaders())
376 - *
377 - * @param $dependencies Mixed: module name (string) or array of module names
378 - */
379 - public function addDependencies( $dependencies ) {
380 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
381 - }
382 -
383 - /**
384 - * Add debug scripts to the module. These scripts are only included
385 - * in debug mode.
386 - *
387 - * @param $scripts Mixed: path to script file (string) or array of paths
388 - */
389 - public function addDebugScripts( $scripts ) {
390 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
391 - }
392 -
393 - /**
394 - * Add language-specific scripts. These scripts are only included for
395 - * a given language.
396 - *
397 - * @param $lang String: language code
398 - * @param $scripts Mixed: path to script file (string) or array of paths
399 - */
400 - public function addLanguageScripts( $lang, $scripts ) {
401 - $this->languageScripts = array_merge_recursive(
402 - $this->languageScripts,
403 - array( $lang => $scripts )
404 - );
405 - }
406 -
407 - /**
408 - * Add skin-specific scripts. These scripts are only included for
409 - * a given skin.
410 - *
411 - * @param $skin String: skin name, or 'default'
412 - * @param $scripts Mixed: path to script file (string) or array of paths
413 - */
414 - public function addSkinScripts( $skin, $scripts ) {
415 - $this->skinScripts = array_merge_recursive(
416 - $this->skinScripts,
417 - array( $skin => $scripts )
418 - );
419 - }
420 -
421 - /**
422 - * Add skin-specific CSS. These CSS files are only included for a
423 - * given skin. If there are no skin-specific CSS files for a skin,
424 - * the files defined for 'default' will be used, if any.
425 - *
426 - * @param $skin String: skin name, or 'default'
427 - * @param $scripts Mixed: path to CSS file (string) or array of paths
428 - */
429 - public function addSkinStyles( $skin, $scripts ) {
430 - $this->skinStyles = array_merge_recursive(
431 - $this->skinStyles,
432 - array( $skin => $scripts )
433 - );
434 - }
435 -
436 - /**
437 - * Add loader scripts. These scripts are loaded on every page and are
438 - * responsible for registering this module using
439 - * mediaWiki.loader.register(). If there are no loader scripts defined,
440 - * the resource loader will register the module itself.
441 - *
442 - * Loader scripts are used to determine a module's dependencies
443 - * dynamically on the client side (e.g. based on browser type/version).
444 - * Note that loader scripts are included on every page, so they should
445 - * be lightweight and use mediaWiki.loader.register()'s callback
446 - * feature to defer dependency calculation.
447 - *
448 - * @param $scripts Mixed: path to script file (string) or array of paths
449 - */
450 - public function addLoaders( $scripts ) {
451 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
452 - }
453 -
454 - public function getScript( ResourceLoaderContext $context ) {
455 - $retval = $this->getPrimaryScript() . "\n" .
456 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
457 - $this->getSkinScript( $context->getSkin() );
458 -
459 - if ( $context->getDebug() ) {
460 - $retval .= $this->getDebugScript();
461 - }
462 -
463 - return $retval;
464 - }
465 -
466 - public function getStyles( ResourceLoaderContext $context ) {
467 - $styles = array();
468 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
469 - if ( !isset( $styles[$media] ) ) {
470 - $styles[$media] = '';
471 - }
472 - $styles[$media] .= $style;
473 - }
474 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
475 - if ( !isset( $styles[$media] ) ) {
476 - $styles[$media] = '';
477 - }
478 - $styles[$media] .= $style;
479 - }
480 -
481 - // Collect referenced files
482 - $files = array();
483 - foreach ( $styles as $style ) {
484 - // Extract and store the list of referenced files
485 - $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
486 - }
487 -
488 - // Only store if modified
489 - if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
490 - $encFiles = FormatJson::encode( $files );
491 - $dbw = wfGetDB( DB_MASTER );
492 - $dbw->replace( 'module_deps',
493 - array( array( 'md_module', 'md_skin' ) ), array(
494 - 'md_module' => $this->getName(),
495 - 'md_skin' => $context->getSkin(),
496 - 'md_deps' => $encFiles,
497 - )
498 - );
499 - }
500 -
501 - return $styles;
502 - }
503 -
504 - public function getMessages() {
505 - return $this->messages;
506 - }
507 -
508 - public function getGroup() {
509 - return $this->group;
510 - }
511 -
512 - public function getDependencies() {
513 - return $this->dependencies;
514 - }
515 -
516 - public function getLoaderScript() {
517 - if ( count( $this->loaders ) == 0 ) {
518 - return false;
519 - }
520 -
521 - return self::concatScripts( $this->loaders );
522 - }
523 -
524 - /**
525 - * Get the last modified timestamp of this module, which is calculated
526 - * as the highest last modified timestamp of its constituent files and
527 - * the files it depends on (see getFileDependencies()). Only files
528 - * relevant to the given language and skin are taken into account, and
529 - * files only relevant in debug mode are not taken into account when
530 - * debug mode is off.
531 - *
532 - * @param $context ResourceLoaderContext object
533 - * @return Integer: UNIX timestamp
534 - */
535 - public function getModifiedTime( ResourceLoaderContext $context ) {
536 - if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
537 - return $this->modifiedTime[$context->getHash()];
538 - }
539 - wfProfileIn( __METHOD__ );
540 -
541 - // Sort of nasty way we can get a flat list of files depended on by all styles
542 - $styles = array();
543 - foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
544 - $styles = array_merge( $styles, $styleFiles );
545 - }
546 - $skinFiles = (array) self::getSkinFiles(
547 - $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' )
548 - );
549 - foreach ( $skinFiles as $styleFiles ) {
550 - $styles = array_merge( $styles, $styleFiles );
551 - }
552 -
553 - // Final merge, this should result in a master list of dependent files
554 - $files = array_merge(
555 - $this->scripts,
556 - $styles,
557 - $context->getDebug() ? $this->debugScripts : array(),
558 - isset( $this->languageScripts[$context->getLanguage()] ) ?
559 - (array) $this->languageScripts[$context->getLanguage()] : array(),
560 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
561 - $this->loaders,
562 - $this->getFileDependencies( $context->getSkin() )
563 - );
564 -
565 - wfProfileIn( __METHOD__.'-filemtime' );
566 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
567 - wfProfileOut( __METHOD__.'-filemtime' );
568 - $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
569 - wfProfileOut( __METHOD__ );
570 - return $this->modifiedTime[$context->getHash()];
571 - }
572 -
573 - /* Protected Members */
574 -
575 - /**
576 - * Get the primary JS for this module. This is pulled from the
577 - * script files added through addScripts()
578 - *
579 - * @return String: JS
580 - */
581 - protected function getPrimaryScript() {
582 - return self::concatScripts( $this->scripts );
583 - }
584 -
585 - /**
586 - * Get the primary CSS for this module. This is pulled from the CSS
587 - * files added through addStyles()
588 - *
589 - * @return Array
590 - */
591 - protected function getPrimaryStyles() {
592 - return self::concatStyles( $this->styles );
593 - }
594 -
595 - /**
596 - * Get the debug JS for this module. This is pulled from the script
597 - * files added through addDebugScripts()
598 - *
599 - * @return String: JS
600 - */
601 - protected function getDebugScript() {
602 - return self::concatScripts( $this->debugScripts );
603 - }
604 -
605 - /**
606 - * Get the language-specific JS for a given language. This is pulled
607 - * from the language-specific script files added through addLanguageScripts()
608 - *
609 - * @return String: JS
610 - */
611 - protected function getLanguageScript( $lang ) {
612 - if ( !isset( $this->languageScripts[$lang] ) ) {
613 - return '';
614 - }
615 - return self::concatScripts( $this->languageScripts[$lang] );
616 - }
617 -
618 - /**
619 - * Get the skin-specific JS for a given skin. This is pulled from the
620 - * skin-specific JS files added through addSkinScripts()
621 - *
622 - * @return String: JS
623 - */
624 - protected function getSkinScript( $skin ) {
625 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
626 - }
627 -
628 - /**
629 - * Get the skin-specific CSS for a given skin. This is pulled from the
630 - * skin-specific CSS files added through addSkinStyles()
631 - *
632 - * @return Array: list of CSS strings keyed by media type
633 - */
634 - protected function getSkinStyles( $skin ) {
635 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
636 - }
637 -
638 - /**
639 - * Helper function to get skin-specific data from an array.
640 - *
641 - * @param $skin String: skin name
642 - * @param $map Array: map of skin names to arrays
643 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
644 - */
645 - protected static function getSkinFiles( $skin, $map ) {
646 - $retval = array();
647 -
648 - if ( isset( $map[$skin] ) && $map[$skin] ) {
649 - $retval = $map[$skin];
650 - } else if ( isset( $map['default'] ) ) {
651 - $retval = $map['default'];
652 - }
653 -
654 - return $retval;
655 - }
656 -
657 - /**
658 - * Get the contents of a set of files and concatenate them, with
659 - * newlines in between. Each file is used only once.
660 - *
661 - * @param $files Array of file names
662 - * @return String: concatenated contents of $files
663 - */
664 - protected static function concatScripts( $files ) {
665 - return implode( "\n",
666 - array_map(
667 - 'file_get_contents',
668 - array_map(
669 - array( __CLASS__, 'remapFilename' ),
670 - array_unique( (array) $files ) ) ) );
671 - }
672 -
673 - protected static function organizeFilesByOption( $files, $option, $default ) {
674 - $organizedFiles = array();
675 - foreach ( (array) $files as $key => $value ) {
676 - if ( is_int( $key ) ) {
677 - // File name as the value
678 - if ( !isset( $organizedFiles[$default] ) ) {
679 - $organizedFiles[$default] = array();
680 - }
681 - $organizedFiles[$default][] = $value;
682 - } else if ( is_array( $value ) ) {
683 - // File name as the key, options array as the value
684 - $media = isset( $value[$option] ) ? $value[$option] : $default;
685 - if ( !isset( $organizedFiles[$media] ) ) {
686 - $organizedFiles[$media] = array();
687 - }
688 - $organizedFiles[$media][] = $key;
689 - }
690 - }
691 - return $organizedFiles;
692 - }
693 -
694 - /**
695 - * Get the contents of a set of CSS files, remap then and concatenate
696 - * them, with newlines in between. Each file is used only once.
697 - *
698 - * @param $styles Array of file names
699 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
700 - */
701 - protected static function concatStyles( $styles ) {
702 - $styles = self::organizeFilesByOption( $styles, 'media', 'all' );
703 - foreach ( $styles as $media => $files ) {
704 - $styles[$media] =
705 - implode( "\n",
706 - array_map(
707 - array( __CLASS__, 'remapStyle' ),
708 - array_unique( (array) $files ) ) );
709 - }
710 - return $styles;
711 - }
712 -
713 - /**
714 - * Remap a relative to $IP. Used as a callback for array_map()
715 - *
716 - * @param $file String: file name
717 - * @return string $IP/$file
718 - */
719 - protected static function remapFilename( $file ) {
720 - global $IP;
721 -
722 - return "$IP/$file";
723 - }
724 -
725 - /**
726 - * Get the contents of a CSS file and run it through CSSMin::remap().
727 - * This wrapper is needed so we can use array_map() in concatStyles()
728 - *
729 - * @param $file String: file name
730 - * @return string Remapped CSS
731 - */
732 - protected static function remapStyle( $file ) {
733 - global $wgScriptPath;
734 - return CSSMin::remap(
735 - file_get_contents( self::remapFilename( $file ) ),
736 - dirname( $file ),
737 - $wgScriptPath . '/' . dirname( $file ),
738 - true
739 - );
740 - }
741 -}
742 -
743 -/**
744 - * Abstraction for resource loader modules which pull from wiki pages
745 - *
746 - * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
747 - * functionality of Title::isValidCssJsSubpage.
748 - */
749 -abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
750 -
751 - /* Protected Members */
752 -
753 - // In-object cache for modified time
754 - protected $modifiedTime = array();
755 -
756 - /* Abstract Protected Methods */
757 -
758 - abstract protected function getPages( ResourceLoaderContext $context );
759 -
760 - /* Protected Methods */
761 -
762 - protected function getContent( $page, $ns ) {
763 - if ( $ns === NS_MEDIAWIKI ) {
764 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
765 - }
766 - if ( $title = Title::newFromText( $page, $ns ) ) {
767 - if ( $title->isValidCssJsSubpage() && $revision = Revision::newFromTitle( $title ) ) {
768 - return $revision->getRawText();
769 - }
770 - }
771 - return null;
772 - }
773 -
774 - /* Methods */
775 -
776 - public function getScript( ResourceLoaderContext $context ) {
777 - $scripts = '';
778 - foreach ( $this->getPages( $context ) as $page => $options ) {
779 - if ( $options['type'] === 'script' ) {
780 - if ( $script = $this->getContent( $page, $options['ns'] ) ) {
781 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
782 - $scripts .= "/*$ns:$page */\n$script\n";
783 - }
784 - }
785 - }
786 - return $scripts;
787 - }
788 -
789 - public function getStyles( ResourceLoaderContext $context ) {
790 -
791 - $styles = array();
792 - foreach ( $this->getPages( $context ) as $page => $options ) {
793 - if ( $options['type'] === 'style' ) {
794 - $media = isset( $options['media'] ) ? $options['media'] : 'all';
795 - if ( $style = $this->getContent( $page, $options['ns'] ) ) {
796 - if ( !isset( $styles[$media] ) ) {
797 - $styles[$media] = '';
798 - }
799 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
800 - $styles[$media] .= "/* $ns:$page */\n$style\n";
801 - }
802 - }
803 - }
804 - return $styles;
805 - }
806 -
807 - public function getModifiedTime( ResourceLoaderContext $context ) {
808 - $hash = $context->getHash();
809 - if ( isset( $this->modifiedTime[$hash] ) ) {
810 - return $this->modifiedTime[$hash];
811 - }
812 -
813 - $titles = array();
814 - foreach ( $this->getPages( $context ) as $page => $options ) {
815 - $titles[$options['ns']][$page] = true;
816 - }
817 -
818 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
819 -
820 - if ( $titles ) {
821 - $dbr = wfGetDB( DB_SLAVE );
822 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
823 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
824 - __METHOD__ );
825 -
826 - if ( $latest ) {
827 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
828 - }
829 - }
830 -
831 - return $this->modifiedTime[$hash] = $modifiedTime;
832 - }
833 -}
834 -
835 -/**
836 - * Module for site customizations
837 - */
838 -class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
839 -
840 - /* Protected Methods */
841 -
842 - protected function getPages( ResourceLoaderContext $context ) {
843 - global $wgHandheldStyle;
844 -
845 - $pages = array(
846 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
847 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
848 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
849 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
850 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
851 - );
852 - if ( $wgHandheldStyle ) {
853 - $pages['Handheld.css'] = array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'handheld' );
854 - }
855 - return $pages;
856 - }
857 -
858 - /* Methods */
859 -
860 - public function getGroup() {
861 - return 'site';
862 - }
863 -}
864 -
865 -/**
86627 * Module for user customizations
86728 */
86829 class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
@@ -889,280 +50,3 @@
89051 return 'user';
89152 }
89253 }
893 -
894 -/**
895 - * Module for user preference customizations
896 - */
897 -class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
898 -
899 - /* Protected Members */
900 -
901 - protected $modifiedTime = array();
902 -
903 - /* Methods */
904 -
905 - public function getModifiedTime( ResourceLoaderContext $context ) {
906 - $hash = $context->getHash();
907 - if ( isset( $this->modifiedTime[$hash] ) ) {
908 - return $this->modifiedTime[$hash];
909 - }
910 -
911 - global $wgUser;
912 -
913 - if ( $context->getUser() === $wgUser->getName() ) {
914 - return $this->modifiedTime[$hash] = $wgUser->getTouched();
915 - } else {
916 - return 1;
917 - }
918 - }
919 -
920 - /**
921 - * Fetch the context's user options, or if it doesn't match current user,
922 - * the default options.
923 - *
924 - * @param $context ResourceLoaderContext
925 - * @return array
926 - */
927 - protected function contextUserOptions( ResourceLoaderContext $context ) {
928 - global $wgUser;
929 -
930 - // Verify identity -- this is a private module
931 - if ( $context->getUser() === $wgUser->getName() ) {
932 - return $wgUser->getOptions();
933 - } else {
934 - return User::getDefaultOptions();
935 - }
936 - }
937 -
938 - public function getScript( ResourceLoaderContext $context ) {
939 - $encOptions = FormatJson::encode( $this->contextUserOptions( $context ) );
940 - return "mediaWiki.user.options.set( $encOptions );";
941 - }
942 -
943 - public function getStyles( ResourceLoaderContext $context ) {
944 - global $wgAllowUserCssPrefs;
945 -
946 - if ( $wgAllowUserCssPrefs ) {
947 - $options = $this->contextUserOptions( $context );
948 -
949 - // Build CSS rules
950 - $rules = array();
951 - if ( $options['underline'] < 2 ) {
952 - $rules[] = "a { text-decoration: " . ( $options['underline'] ? 'underline' : 'none' ) . "; }";
953 - }
954 - if ( $options['highlightbroken'] ) {
955 - $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
956 - } else {
957 - $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
958 - $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
959 - $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
960 - }
961 - if ( $options['justify'] ) {
962 - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
963 - }
964 - if ( !$options['showtoc'] ) {
965 - $rules[] = "#toc { display: none; }\n";
966 - }
967 - if ( !$options['editsection'] ) {
968 - $rules[] = ".editsection { display: none; }\n";
969 - }
970 - if ( $options['editfont'] !== 'default' ) {
971 - $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
972 - }
973 - return array( 'all' => implode( "\n", $rules ) );
974 - }
975 - return array();
976 - }
977 -
978 - public function getFlip( $context ) {
979 - global $wgContLang;
980 -
981 - return $wgContLang->getDir() !== $context->getDirection();
982 - }
983 -
984 - public function getGroup() {
985 - return 'private';
986 - }
987 -}
988 -
989 -class ResourceLoaderStartUpModule extends ResourceLoaderModule {
990 - /* Protected Members */
991 -
992 - protected $modifiedTime = array();
993 -
994 - /* Protected Methods */
995 -
996 - protected function getConfig( $context ) {
997 - global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
998 - $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgBreakFrames,
999 - $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
1000 - $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
1001 - $wgSitename, $wgFileExtensions;
1002 -
1003 - // Pre-process information
1004 - $separatorTransTable = $wgContLang->separatorTransformTable();
1005 - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
1006 - $compactSeparatorTransTable = array(
1007 - implode( "\t", array_keys( $separatorTransTable ) ),
1008 - implode( "\t", $separatorTransTable ),
1009 - );
1010 - $digitTransTable = $wgContLang->digitTransformTable();
1011 - $digitTransTable = $digitTransTable ? $digitTransTable : array();
1012 - $compactDigitTransTable = array(
1013 - implode( "\t", array_keys( $digitTransTable ) ),
1014 - implode( "\t", $digitTransTable ),
1015 - );
1016 - $mainPage = Title::newMainPage();
1017 -
1018 - // Build list of variables
1019 - $vars = array(
1020 - 'wgLoadScript' => $wgLoadScript,
1021 - 'debug' => $context->getDebug(),
1022 - 'skin' => $context->getSkin(),
1023 - 'stylepath' => $wgStylePath,
1024 - 'wgUrlProtocols' => wfUrlProtocols(),
1025 - 'wgArticlePath' => $wgArticlePath,
1026 - 'wgScriptPath' => $wgScriptPath,
1027 - 'wgScriptExtension' => $wgScriptExtension,
1028 - 'wgScript' => $wgScript,
1029 - 'wgVariantArticlePath' => $wgVariantArticlePath,
1030 - 'wgActionPaths' => $wgActionPaths,
1031 - 'wgServer' => $wgServer,
1032 - 'wgUserLanguage' => $context->getLanguage(),
1033 - 'wgContentLanguage' => $wgContLang->getCode(),
1034 - 'wgBreakFrames' => $wgBreakFrames,
1035 - 'wgVersion' => $wgVersion,
1036 - 'wgEnableAPI' => $wgEnableAPI,
1037 - 'wgEnableWriteAPI' => $wgEnableWriteAPI,
1038 - 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
1039 - 'wgDigitTransformTable' => $compactDigitTransTable,
1040 - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
1041 - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
1042 - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
1043 - 'wgSiteName' => $wgSitename,
1044 - 'wgFileExtensions' => $wgFileExtensions,
1045 - 'wgDBname' => $wgDBname,
1046 - );
1047 - if ( $wgContLang->hasVariants() ) {
1048 - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
1049 - }
1050 - if ( $wgUseAjax && $wgEnableMWSuggest ) {
1051 - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
1052 - }
1053 -
1054 - return $vars;
1055 - }
1056 -
1057 - /**
1058 - * Gets registration code for all modules
1059 - *
1060 - * @param $context ResourceLoaderContext object
1061 - * @return String: JavaScript code for registering all modules with the client loader
1062 - */
1063 - public static function getModuleRegistrations( ResourceLoaderContext $context ) {
1064 - global $wgCacheEpoch;
1065 - wfProfileIn( __METHOD__ );
1066 -
1067 - $out = '';
1068 - $registrations = array();
1069 - foreach ( $context->getResourceLoader()->getModules() as $name => $module ) {
1070 - // Support module loader scripts
1071 - if ( ( $loader = $module->getLoaderScript() ) !== false ) {
1072 - $deps = $module->getDependencies();
1073 - $group = $module->getGroup();
1074 - $version = wfTimestamp( TS_ISO_8601_BASIC, round( $module->getModifiedTime( $context ), -2 ) );
1075 - $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
1076 - }
1077 - // Automatically register module
1078 - else {
1079 - $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
1080 - // Modules without dependencies or a group pass two arguments (name, timestamp) to
1081 - // mediaWiki.loader.register()
1082 - if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
1083 - $registrations[] = array( $name, $mtime );
1084 - }
1085 - // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies)
1086 - // to mediaWiki.loader.register()
1087 - else if ( $module->getGroup() === null ) {
1088 - $registrations[] = array(
1089 - $name, $mtime, $module->getDependencies() );
1090 - }
1091 - // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
1092 - // to mediaWiki.loader.register()
1093 - else {
1094 - $registrations[] = array(
1095 - $name, $mtime, $module->getDependencies(), $module->getGroup() );
1096 - }
1097 - }
1098 - }
1099 - $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
1100 -
1101 - wfProfileOut( __METHOD__ );
1102 - return $out;
1103 - }
1104 -
1105 - /* Methods */
1106 -
1107 - public function getScript( ResourceLoaderContext $context ) {
1108 - global $IP, $wgLoadScript;
1109 -
1110 - $out = file_get_contents( "$IP/resources/startup.js" );
1111 - if ( $context->getOnly() === 'scripts' ) {
1112 - // Build load query for jquery and mediawiki modules
1113 - $query = array(
1114 - 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
1115 - 'only' => 'scripts',
1116 - 'lang' => $context->getLanguage(),
1117 - 'skin' => $context->getSkin(),
1118 - 'debug' => $context->getDebug() ? 'true' : 'false',
1119 - 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
1120 - $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
1121 - $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
1122 - ), -2 ) )
1123 - );
1124 - // Ensure uniform query order
1125 - ksort( $query );
1126 -
1127 - // Startup function
1128 - $configuration = FormatJson::encode( $this->getConfig( $context ) );
1129 - $registrations = self::getModuleRegistrations( $context );
1130 - $out .= "var startUp = function() {\n\t$registrations\n\tmediaWiki.config.set( $configuration );\n};";
1131 -
1132 - // Conditional script injection
1133 - $scriptTag = Xml::escapeJsString( Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) );
1134 - $out .= "if ( isCompatible() ) {\n\tdocument.write( '$scriptTag' );\n}\ndelete isCompatible;";
1135 - }
1136 -
1137 - return $out;
1138 - }
1139 -
1140 - public function getModifiedTime( ResourceLoaderContext $context ) {
1141 - global $IP, $wgCacheEpoch;
1142 -
1143 - $hash = $context->getHash();
1144 - if ( isset( $this->modifiedTime[$hash] ) ) {
1145 - return $this->modifiedTime[$hash];
1146 - }
1147 - $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
1148 -
1149 - // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
1150 - // before making changes to this code!
1151 - $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
1152 - foreach ( $context->getResourceLoader()->getModules() as $module ) {
1153 - $time = max( $time, $module->getModifiedTime( $context ) );
1154 - }
1155 - return $this->modifiedTime[$hash] = $time;
1156 - }
1157 -
1158 - public function getFlip( $context ) {
1159 - global $wgContLang;
1160 -
1161 - return $wgContLang->getDir() !== $context->getDirection();
1162 - }
1163 -
1164 - /* Methods */
1165 -
1166 - public function getGroup() {
1167 - return 'startup';
1168 - }
1169 -}
Index: trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php
@@ -23,207 +23,6 @@
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
2626 /**
27 - * Abstraction for resource loader modules, with name registration and maxage functionality.
28 - */
29 -abstract class ResourceLoaderModule {
30 -
31 - /* Protected Members */
32 -
33 - protected $name = null;
34 -
35 - // In-object cache for file dependencies
36 - protected $fileDeps = array();
37 - // In-object cache for message blob mtime
38 - protected $msgBlobMtime = array();
39 -
40 - /* Methods */
41 -
42 - /**
43 - * Get this module's name. This is set when the module is registered
44 - * with ResourceLoader::register()
45 - *
46 - * @return Mixed: name (string) or null if no name was set
47 - */
48 - public function getName() {
49 - return $this->name;
50 - }
51 -
52 - /**
53 - * Set this module's name. This is called by ResourceLodaer::register()
54 - * when registering the module. Other code should not call this.
55 - *
56 - * @param $name String: name
57 - */
58 - public function setName( $name ) {
59 - $this->name = $name;
60 - }
61 -
62 - /**
63 - * Get whether CSS for this module should be flipped
64 - */
65 - public function getFlip( $context ) {
66 - return $context->getDirection() === 'rtl';
67 - }
68 -
69 - /**
70 - * Get all JS for this module for a given language and skin.
71 - * Includes all relevant JS except loader scripts.
72 - *
73 - * @param $context ResourceLoaderContext object
74 - * @return String: JS
75 - */
76 - public function getScript( ResourceLoaderContext $context ) {
77 - // Stub, override expected
78 - return '';
79 - }
80 -
81 - /**
82 - * Get all CSS for this module for a given skin.
83 - *
84 - * @param $context ResourceLoaderContext object
85 - * @return array: strings of CSS keyed by media type
86 - */
87 - public function getStyles( ResourceLoaderContext $context ) {
88 - // Stub, override expected
89 - return '';
90 - }
91 -
92 - /**
93 - * Get the messages needed for this module.
94 - *
95 - * To get a JSON blob with messages, use MessageBlobStore::get()
96 - *
97 - * @return array of message keys. Keys may occur more than once
98 - */
99 - public function getMessages() {
100 - // Stub, override expected
101 - return array();
102 - }
103 -
104 - /**
105 - * Get the group this module is in.
106 - *
107 - * @return string of group name
108 - */
109 - public function getGroup() {
110 - // Stub, override expected
111 - return null;
112 - }
113 -
114 - /**
115 - * Get the loader JS for this module, if set.
116 - *
117 - * @return Mixed: loader JS (string) or false if no custom loader set
118 - */
119 - public function getLoaderScript() {
120 - // Stub, override expected
121 - return false;
122 - }
123 -
124 - /**
125 - * Get a list of modules this module depends on.
126 - *
127 - * Dependency information is taken into account when loading a module
128 - * on the client side. When adding a module on the server side,
129 - * dependency information is NOT taken into account and YOU are
130 - * responsible for adding dependent modules as well. If you don't do
131 - * this, the client side loader will send a second request back to the
132 - * server to fetch the missing modules, which kind of defeats the
133 - * purpose of the resource loader.
134 - *
135 - * To add dependencies dynamically on the client side, use a custom
136 - * loader script, see getLoaderScript()
137 - * @return Array of module names (strings)
138 - */
139 - public function getDependencies() {
140 - // Stub, override expected
141 - return array();
142 - }
143 -
144 - /**
145 - * Get the files this module depends on indirectly for a given skin.
146 - * Currently these are only image files referenced by the module's CSS.
147 - *
148 - * @param $skin String: skin name
149 - * @return array of files
150 - */
151 - public function getFileDependencies( $skin ) {
152 - // Try in-object cache first
153 - if ( isset( $this->fileDeps[$skin] ) ) {
154 - return $this->fileDeps[$skin];
155 - }
156 -
157 - $dbr = wfGetDB( DB_SLAVE );
158 - $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
159 - 'md_module' => $this->getName(),
160 - 'md_skin' => $skin,
161 - ), __METHOD__
162 - );
163 - if ( !is_null( $deps ) ) {
164 - return $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
165 - }
166 - return $this->fileDeps[$skin] = array();
167 - }
168 -
169 - /**
170 - * Set preloaded file dependency information. Used so we can load this
171 - * information for all modules at once.
172 - * @param $skin string Skin name
173 - * @param $deps array Array of file names
174 - */
175 - public function setFileDependencies( $skin, $deps ) {
176 - $this->fileDeps[$skin] = $deps;
177 - }
178 -
179 - /**
180 - * Get the last modification timestamp of the message blob for this
181 - * module in a given language.
182 - * @param $lang string Language code
183 - * @return int UNIX timestamp, or 0 if no blob found
184 - */
185 - public function getMsgBlobMtime( $lang ) {
186 - if ( !count( $this->getMessages() ) )
187 - return 0;
188 -
189 - $dbr = wfGetDB( DB_SLAVE );
190 - $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
191 - 'mr_resource' => $this->getName(),
192 - 'mr_lang' => $lang
193 - ), __METHOD__
194 - );
195 - $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
196 - return $this->msgBlobMtime[$lang];
197 - }
198 -
199 - /**
200 - * Set a preloaded message blob last modification timestamp. Used so we
201 - * can load this information for all modules at once.
202 - * @param $lang string Language code
203 - * @param $mtime int UNIX timestamp or 0 if there is no such blob
204 - */
205 - public function setMsgBlobMtime( $lang, $mtime ) {
206 - $this->msgBlobMtime[$lang] = $mtime;
207 - }
208 -
209 - /* Abstract Methods */
210 -
211 - /**
212 - * Get this module's last modification timestamp for a given
213 - * combination of language, skin and debug mode flag. This is typically
214 - * the highest of each of the relevant components' modification
215 - * timestamps. Whenever anything happens that changes the module's
216 - * contents for these parameters, the mtime should increase.
217 - *
218 - * @param $context ResourceLoaderContext object
219 - * @return int UNIX timestamp
220 - */
221 - public function getModifiedTime( ResourceLoaderContext $context ) {
222 - // 0 would mean now
223 - return 1;
224 - }
225 -}
226 -
227 -/**
22827 * Module based on local JS/CSS files. This is the most common type of module.
22928 */
23029 class ResourceLoaderFileModule extends ResourceLoaderModule {
@@ -738,431 +537,3 @@
739538 );
740539 }
741540 }
742 -
743 -/**
744 - * Abstraction for resource loader modules which pull from wiki pages
745 - *
746 - * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
747 - * functionality of Title::isValidCssJsSubpage.
748 - */
749 -abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
750 -
751 - /* Protected Members */
752 -
753 - // In-object cache for modified time
754 - protected $modifiedTime = array();
755 -
756 - /* Abstract Protected Methods */
757 -
758 - abstract protected function getPages( ResourceLoaderContext $context );
759 -
760 - /* Protected Methods */
761 -
762 - protected function getContent( $page, $ns ) {
763 - if ( $ns === NS_MEDIAWIKI ) {
764 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
765 - }
766 - if ( $title = Title::newFromText( $page, $ns ) ) {
767 - if ( $title->isValidCssJsSubpage() && $revision = Revision::newFromTitle( $title ) ) {
768 - return $revision->getRawText();
769 - }
770 - }
771 - return null;
772 - }
773 -
774 - /* Methods */
775 -
776 - public function getScript( ResourceLoaderContext $context ) {
777 - $scripts = '';
778 - foreach ( $this->getPages( $context ) as $page => $options ) {
779 - if ( $options['type'] === 'script' ) {
780 - if ( $script = $this->getContent( $page, $options['ns'] ) ) {
781 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
782 - $scripts .= "/*$ns:$page */\n$script\n";
783 - }
784 - }
785 - }
786 - return $scripts;
787 - }
788 -
789 - public function getStyles( ResourceLoaderContext $context ) {
790 -
791 - $styles = array();
792 - foreach ( $this->getPages( $context ) as $page => $options ) {
793 - if ( $options['type'] === 'style' ) {
794 - $media = isset( $options['media'] ) ? $options['media'] : 'all';
795 - if ( $style = $this->getContent( $page, $options['ns'] ) ) {
796 - if ( !isset( $styles[$media] ) ) {
797 - $styles[$media] = '';
798 - }
799 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
800 - $styles[$media] .= "/* $ns:$page */\n$style\n";
801 - }
802 - }
803 - }
804 - return $styles;
805 - }
806 -
807 - public function getModifiedTime( ResourceLoaderContext $context ) {
808 - $hash = $context->getHash();
809 - if ( isset( $this->modifiedTime[$hash] ) ) {
810 - return $this->modifiedTime[$hash];
811 - }
812 -
813 - $titles = array();
814 - foreach ( $this->getPages( $context ) as $page => $options ) {
815 - $titles[$options['ns']][$page] = true;
816 - }
817 -
818 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
819 -
820 - if ( $titles ) {
821 - $dbr = wfGetDB( DB_SLAVE );
822 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
823 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
824 - __METHOD__ );
825 -
826 - if ( $latest ) {
827 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
828 - }
829 - }
830 -
831 - return $this->modifiedTime[$hash] = $modifiedTime;
832 - }
833 -}
834 -
835 -/**
836 - * Module for site customizations
837 - */
838 -class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
839 -
840 - /* Protected Methods */
841 -
842 - protected function getPages( ResourceLoaderContext $context ) {
843 - global $wgHandheldStyle;
844 -
845 - $pages = array(
846 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
847 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
848 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
849 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
850 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
851 - );
852 - if ( $wgHandheldStyle ) {
853 - $pages['Handheld.css'] = array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'handheld' );
854 - }
855 - return $pages;
856 - }
857 -
858 - /* Methods */
859 -
860 - public function getGroup() {
861 - return 'site';
862 - }
863 -}
864 -
865 -/**
866 - * Module for user customizations
867 - */
868 -class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
869 -
870 - /* Protected Methods */
871 -
872 - protected function getPages( ResourceLoaderContext $context ) {
873 - global $wgAllowUserCss;
874 -
875 - if ( $context->getUser() && $wgAllowUserCss ) {
876 - $username = $context->getUser();
877 - return array(
878 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
879 - "$username/" . $context->getSkin() . '.js' => array( 'ns' => NS_USER, 'type' => 'script' ),
880 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
881 - "$username/" . $context->getSkin() . '.css' => array( 'ns' => NS_USER, 'type' => 'style' ),
882 - );
883 - }
884 - return array();
885 - }
886 -
887 - /* Methods */
888 -
889 - public function getGroup() {
890 - return 'user';
891 - }
892 -}
893 -
894 -/**
895 - * Module for user preference customizations
896 - */
897 -class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
898 -
899 - /* Protected Members */
900 -
901 - protected $modifiedTime = array();
902 -
903 - /* Methods */
904 -
905 - public function getModifiedTime( ResourceLoaderContext $context ) {
906 - $hash = $context->getHash();
907 - if ( isset( $this->modifiedTime[$hash] ) ) {
908 - return $this->modifiedTime[$hash];
909 - }
910 -
911 - global $wgUser;
912 -
913 - if ( $context->getUser() === $wgUser->getName() ) {
914 - return $this->modifiedTime[$hash] = $wgUser->getTouched();
915 - } else {
916 - return 1;
917 - }
918 - }
919 -
920 - /**
921 - * Fetch the context's user options, or if it doesn't match current user,
922 - * the default options.
923 - *
924 - * @param $context ResourceLoaderContext
925 - * @return array
926 - */
927 - protected function contextUserOptions( ResourceLoaderContext $context ) {
928 - global $wgUser;
929 -
930 - // Verify identity -- this is a private module
931 - if ( $context->getUser() === $wgUser->getName() ) {
932 - return $wgUser->getOptions();
933 - } else {
934 - return User::getDefaultOptions();
935 - }
936 - }
937 -
938 - public function getScript( ResourceLoaderContext $context ) {
939 - $encOptions = FormatJson::encode( $this->contextUserOptions( $context ) );
940 - return "mediaWiki.user.options.set( $encOptions );";
941 - }
942 -
943 - public function getStyles( ResourceLoaderContext $context ) {
944 - global $wgAllowUserCssPrefs;
945 -
946 - if ( $wgAllowUserCssPrefs ) {
947 - $options = $this->contextUserOptions( $context );
948 -
949 - // Build CSS rules
950 - $rules = array();
951 - if ( $options['underline'] < 2 ) {
952 - $rules[] = "a { text-decoration: " . ( $options['underline'] ? 'underline' : 'none' ) . "; }";
953 - }
954 - if ( $options['highlightbroken'] ) {
955 - $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
956 - } else {
957 - $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
958 - $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
959 - $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
960 - }
961 - if ( $options['justify'] ) {
962 - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
963 - }
964 - if ( !$options['showtoc'] ) {
965 - $rules[] = "#toc { display: none; }\n";
966 - }
967 - if ( !$options['editsection'] ) {
968 - $rules[] = ".editsection { display: none; }\n";
969 - }
970 - if ( $options['editfont'] !== 'default' ) {
971 - $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
972 - }
973 - return array( 'all' => implode( "\n", $rules ) );
974 - }
975 - return array();
976 - }
977 -
978 - public function getFlip( $context ) {
979 - global $wgContLang;
980 -
981 - return $wgContLang->getDir() !== $context->getDirection();
982 - }
983 -
984 - public function getGroup() {
985 - return 'private';
986 - }
987 -}
988 -
989 -class ResourceLoaderStartUpModule extends ResourceLoaderModule {
990 - /* Protected Members */
991 -
992 - protected $modifiedTime = array();
993 -
994 - /* Protected Methods */
995 -
996 - protected function getConfig( $context ) {
997 - global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
998 - $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgBreakFrames,
999 - $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
1000 - $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
1001 - $wgSitename, $wgFileExtensions;
1002 -
1003 - // Pre-process information
1004 - $separatorTransTable = $wgContLang->separatorTransformTable();
1005 - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
1006 - $compactSeparatorTransTable = array(
1007 - implode( "\t", array_keys( $separatorTransTable ) ),
1008 - implode( "\t", $separatorTransTable ),
1009 - );
1010 - $digitTransTable = $wgContLang->digitTransformTable();
1011 - $digitTransTable = $digitTransTable ? $digitTransTable : array();
1012 - $compactDigitTransTable = array(
1013 - implode( "\t", array_keys( $digitTransTable ) ),
1014 - implode( "\t", $digitTransTable ),
1015 - );
1016 - $mainPage = Title::newMainPage();
1017 -
1018 - // Build list of variables
1019 - $vars = array(
1020 - 'wgLoadScript' => $wgLoadScript,
1021 - 'debug' => $context->getDebug(),
1022 - 'skin' => $context->getSkin(),
1023 - 'stylepath' => $wgStylePath,
1024 - 'wgUrlProtocols' => wfUrlProtocols(),
1025 - 'wgArticlePath' => $wgArticlePath,
1026 - 'wgScriptPath' => $wgScriptPath,
1027 - 'wgScriptExtension' => $wgScriptExtension,
1028 - 'wgScript' => $wgScript,
1029 - 'wgVariantArticlePath' => $wgVariantArticlePath,
1030 - 'wgActionPaths' => $wgActionPaths,
1031 - 'wgServer' => $wgServer,
1032 - 'wgUserLanguage' => $context->getLanguage(),
1033 - 'wgContentLanguage' => $wgContLang->getCode(),
1034 - 'wgBreakFrames' => $wgBreakFrames,
1035 - 'wgVersion' => $wgVersion,
1036 - 'wgEnableAPI' => $wgEnableAPI,
1037 - 'wgEnableWriteAPI' => $wgEnableWriteAPI,
1038 - 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
1039 - 'wgDigitTransformTable' => $compactDigitTransTable,
1040 - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
1041 - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
1042 - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
1043 - 'wgSiteName' => $wgSitename,
1044 - 'wgFileExtensions' => $wgFileExtensions,
1045 - 'wgDBname' => $wgDBname,
1046 - );
1047 - if ( $wgContLang->hasVariants() ) {
1048 - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
1049 - }
1050 - if ( $wgUseAjax && $wgEnableMWSuggest ) {
1051 - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
1052 - }
1053 -
1054 - return $vars;
1055 - }
1056 -
1057 - /**
1058 - * Gets registration code for all modules
1059 - *
1060 - * @param $context ResourceLoaderContext object
1061 - * @return String: JavaScript code for registering all modules with the client loader
1062 - */
1063 - public static function getModuleRegistrations( ResourceLoaderContext $context ) {
1064 - global $wgCacheEpoch;
1065 - wfProfileIn( __METHOD__ );
1066 -
1067 - $out = '';
1068 - $registrations = array();
1069 - foreach ( $context->getResourceLoader()->getModules() as $name => $module ) {
1070 - // Support module loader scripts
1071 - if ( ( $loader = $module->getLoaderScript() ) !== false ) {
1072 - $deps = $module->getDependencies();
1073 - $group = $module->getGroup();
1074 - $version = wfTimestamp( TS_ISO_8601_BASIC, round( $module->getModifiedTime( $context ), -2 ) );
1075 - $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
1076 - }
1077 - // Automatically register module
1078 - else {
1079 - $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
1080 - // Modules without dependencies or a group pass two arguments (name, timestamp) to
1081 - // mediaWiki.loader.register()
1082 - if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
1083 - $registrations[] = array( $name, $mtime );
1084 - }
1085 - // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies)
1086 - // to mediaWiki.loader.register()
1087 - else if ( $module->getGroup() === null ) {
1088 - $registrations[] = array(
1089 - $name, $mtime, $module->getDependencies() );
1090 - }
1091 - // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
1092 - // to mediaWiki.loader.register()
1093 - else {
1094 - $registrations[] = array(
1095 - $name, $mtime, $module->getDependencies(), $module->getGroup() );
1096 - }
1097 - }
1098 - }
1099 - $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
1100 -
1101 - wfProfileOut( __METHOD__ );
1102 - return $out;
1103 - }
1104 -
1105 - /* Methods */
1106 -
1107 - public function getScript( ResourceLoaderContext $context ) {
1108 - global $IP, $wgLoadScript;
1109 -
1110 - $out = file_get_contents( "$IP/resources/startup.js" );
1111 - if ( $context->getOnly() === 'scripts' ) {
1112 - // Build load query for jquery and mediawiki modules
1113 - $query = array(
1114 - 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
1115 - 'only' => 'scripts',
1116 - 'lang' => $context->getLanguage(),
1117 - 'skin' => $context->getSkin(),
1118 - 'debug' => $context->getDebug() ? 'true' : 'false',
1119 - 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
1120 - $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
1121 - $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
1122 - ), -2 ) )
1123 - );
1124 - // Ensure uniform query order
1125 - ksort( $query );
1126 -
1127 - // Startup function
1128 - $configuration = FormatJson::encode( $this->getConfig( $context ) );
1129 - $registrations = self::getModuleRegistrations( $context );
1130 - $out .= "var startUp = function() {\n\t$registrations\n\tmediaWiki.config.set( $configuration );\n};";
1131 -
1132 - // Conditional script injection
1133 - $scriptTag = Xml::escapeJsString( Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) );
1134 - $out .= "if ( isCompatible() ) {\n\tdocument.write( '$scriptTag' );\n}\ndelete isCompatible;";
1135 - }
1136 -
1137 - return $out;
1138 - }
1139 -
1140 - public function getModifiedTime( ResourceLoaderContext $context ) {
1141 - global $IP, $wgCacheEpoch;
1142 -
1143 - $hash = $context->getHash();
1144 - if ( isset( $this->modifiedTime[$hash] ) ) {
1145 - return $this->modifiedTime[$hash];
1146 - }
1147 - $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
1148 -
1149 - // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
1150 - // before making changes to this code!
1151 - $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
1152 - foreach ( $context->getResourceLoader()->getModules() as $module ) {
1153 - $time = max( $time, $module->getModifiedTime( $context ) );
1154 - }
1155 - return $this->modifiedTime[$hash] = $time;
1156 - }
1157 -
1158 - public function getFlip( $context ) {
1159 - global $wgContLang;
1160 -
1161 - return $wgContLang->getDir() !== $context->getDirection();
1162 - }
1163 -
1164 - /* Methods */
1165 -
1166 - public function getGroup() {
1167 - return 'startup';
1168 - }
1169 -}
Index: trunk/phase3/includes/resourceloader/ResourceLoaderModule.php
@@ -222,947 +222,3 @@
223223 return 1;
224224 }
225225 }
226 -
227 -/**
228 - * Module based on local JS/CSS files. This is the most common type of module.
229 - */
230 -class ResourceLoaderFileModule extends ResourceLoaderModule {
231 - /* Protected Members */
232 -
233 - protected $scripts = array();
234 - protected $styles = array();
235 - protected $messages = array();
236 - protected $group;
237 - protected $dependencies = array();
238 - protected $debugScripts = array();
239 - protected $languageScripts = array();
240 - protected $skinScripts = array();
241 - protected $skinStyles = array();
242 - protected $loaders = array();
243 - protected $parameters = array();
244 -
245 - // In-object cache for file dependencies
246 - protected $fileDeps = array();
247 - // In-object cache for mtime
248 - protected $modifiedTime = array();
249 -
250 - /* Methods */
251 -
252 - /**
253 - * Construct a new module from an options array.
254 - *
255 - * @param $options array Options array. If empty, an empty module will be constructed
256 - *
257 - * $options format:
258 - * array(
259 - * // Required module options (mutually exclusive)
260 - * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ),
261 - *
262 - * // Optional module options
263 - * 'languageScripts' => array(
264 - * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... )
265 - * ...
266 - * ),
267 - * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ),
268 - * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ),
269 - *
270 - * // Non-raw module options
271 - * 'dependencies' => 'module' | array( 'module1', 'module2' ... )
272 - * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ),
273 - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), |
274 - * array( 'dir/file1.css' => array( 'media' => 'print' ) ),
275 - * 'skinStyles' => array(
276 - * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) |
277 - * array( 'dir/file1.css' => array( 'media' => 'print' )
278 - * ...
279 - * ),
280 - * 'messages' => array( 'message1', 'message2' ... ),
281 - * 'group' => 'stuff',
282 - * )
283 - *
284 - * @param $basePath String: base path to prepend to all paths in $options
285 - */
286 - public function __construct( $options = array(), $basePath = null ) {
287 - foreach ( $options as $option => $value ) {
288 - switch ( $option ) {
289 - case 'scripts':
290 - case 'debugScripts':
291 - case 'languageScripts':
292 - case 'skinScripts':
293 - case 'loaders':
294 - $this->{$option} = (array)$value;
295 - // Automatically prefix script paths
296 - if ( is_string( $basePath ) ) {
297 - foreach ( $this->{$option} as $key => $value ) {
298 - $this->{$option}[$key] = $basePath . $value;
299 - }
300 - }
301 - break;
302 - case 'styles':
303 - case 'skinStyles':
304 - $this->{$option} = (array)$value;
305 - // Automatically prefix style paths
306 - if ( is_string( $basePath ) ) {
307 - foreach ( $this->{$option} as $key => $value ) {
308 - if ( is_array( $value ) ) {
309 - $this->{$option}[$basePath . $key] = $value;
310 - unset( $this->{$option}[$key] );
311 - } else {
312 - $this->{$option}[$key] = $basePath . $value;
313 - }
314 - }
315 - }
316 - break;
317 - case 'dependencies':
318 - case 'messages':
319 - $this->{$option} = (array)$value;
320 - break;
321 - case 'group':
322 - $this->group = (string)$value;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - /**
329 - * Add script files to this module. In order to be valid, a module
330 - * must contain at least one script file.
331 - *
332 - * @param $scripts Mixed: path to script file (string) or array of paths
333 - */
334 - public function addScripts( $scripts ) {
335 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
336 - }
337 -
338 - /**
339 - * Add style (CSS) files to this module.
340 - *
341 - * @param $styles Mixed: path to CSS file (string) or array of paths
342 - */
343 - public function addStyles( $styles ) {
344 - $this->styles = array_merge( $this->styles, (array)$styles );
345 - }
346 -
347 - /**
348 - * Add messages to this module.
349 - *
350 - * @param $messages Mixed: message key (string) or array of message keys
351 - */
352 - public function addMessages( $messages ) {
353 - $this->messages = array_merge( $this->messages, (array)$messages );
354 - }
355 -
356 - /**
357 - * Sets the group of this module.
358 - *
359 - * @param $group string group name
360 - */
361 - public function setGroup( $group ) {
362 - $this->group = $group;
363 - }
364 -
365 - /**
366 - * Add dependencies. Dependency information is taken into account when
367 - * loading a module on the client side. When adding a module on the
368 - * server side, dependency information is NOT taken into account and
369 - * YOU are responsible for adding dependent modules as well. If you
370 - * don't do this, the client side loader will send a second request
371 - * back to the server to fetch the missing modules, which kind of
372 - * defeats the point of using the resource loader in the first place.
373 - *
374 - * To add dependencies dynamically on the client side, use a custom
375 - * loader (see addLoaders())
376 - *
377 - * @param $dependencies Mixed: module name (string) or array of module names
378 - */
379 - public function addDependencies( $dependencies ) {
380 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
381 - }
382 -
383 - /**
384 - * Add debug scripts to the module. These scripts are only included
385 - * in debug mode.
386 - *
387 - * @param $scripts Mixed: path to script file (string) or array of paths
388 - */
389 - public function addDebugScripts( $scripts ) {
390 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
391 - }
392 -
393 - /**
394 - * Add language-specific scripts. These scripts are only included for
395 - * a given language.
396 - *
397 - * @param $lang String: language code
398 - * @param $scripts Mixed: path to script file (string) or array of paths
399 - */
400 - public function addLanguageScripts( $lang, $scripts ) {
401 - $this->languageScripts = array_merge_recursive(
402 - $this->languageScripts,
403 - array( $lang => $scripts )
404 - );
405 - }
406 -
407 - /**
408 - * Add skin-specific scripts. These scripts are only included for
409 - * a given skin.
410 - *
411 - * @param $skin String: skin name, or 'default'
412 - * @param $scripts Mixed: path to script file (string) or array of paths
413 - */
414 - public function addSkinScripts( $skin, $scripts ) {
415 - $this->skinScripts = array_merge_recursive(
416 - $this->skinScripts,
417 - array( $skin => $scripts )
418 - );
419 - }
420 -
421 - /**
422 - * Add skin-specific CSS. These CSS files are only included for a
423 - * given skin. If there are no skin-specific CSS files for a skin,
424 - * the files defined for 'default' will be used, if any.
425 - *
426 - * @param $skin String: skin name, or 'default'
427 - * @param $scripts Mixed: path to CSS file (string) or array of paths
428 - */
429 - public function addSkinStyles( $skin, $scripts ) {
430 - $this->skinStyles = array_merge_recursive(
431 - $this->skinStyles,
432 - array( $skin => $scripts )
433 - );
434 - }
435 -
436 - /**
437 - * Add loader scripts. These scripts are loaded on every page and are
438 - * responsible for registering this module using
439 - * mediaWiki.loader.register(). If there are no loader scripts defined,
440 - * the resource loader will register the module itself.
441 - *
442 - * Loader scripts are used to determine a module's dependencies
443 - * dynamically on the client side (e.g. based on browser type/version).
444 - * Note that loader scripts are included on every page, so they should
445 - * be lightweight and use mediaWiki.loader.register()'s callback
446 - * feature to defer dependency calculation.
447 - *
448 - * @param $scripts Mixed: path to script file (string) or array of paths
449 - */
450 - public function addLoaders( $scripts ) {
451 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
452 - }
453 -
454 - public function getScript( ResourceLoaderContext $context ) {
455 - $retval = $this->getPrimaryScript() . "\n" .
456 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
457 - $this->getSkinScript( $context->getSkin() );
458 -
459 - if ( $context->getDebug() ) {
460 - $retval .= $this->getDebugScript();
461 - }
462 -
463 - return $retval;
464 - }
465 -
466 - public function getStyles( ResourceLoaderContext $context ) {
467 - $styles = array();
468 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
469 - if ( !isset( $styles[$media] ) ) {
470 - $styles[$media] = '';
471 - }
472 - $styles[$media] .= $style;
473 - }
474 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
475 - if ( !isset( $styles[$media] ) ) {
476 - $styles[$media] = '';
477 - }
478 - $styles[$media] .= $style;
479 - }
480 -
481 - // Collect referenced files
482 - $files = array();
483 - foreach ( $styles as $style ) {
484 - // Extract and store the list of referenced files
485 - $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
486 - }
487 -
488 - // Only store if modified
489 - if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
490 - $encFiles = FormatJson::encode( $files );
491 - $dbw = wfGetDB( DB_MASTER );
492 - $dbw->replace( 'module_deps',
493 - array( array( 'md_module', 'md_skin' ) ), array(
494 - 'md_module' => $this->getName(),
495 - 'md_skin' => $context->getSkin(),
496 - 'md_deps' => $encFiles,
497 - )
498 - );
499 - }
500 -
501 - return $styles;
502 - }
503 -
504 - public function getMessages() {
505 - return $this->messages;
506 - }
507 -
508 - public function getGroup() {
509 - return $this->group;
510 - }
511 -
512 - public function getDependencies() {
513 - return $this->dependencies;
514 - }
515 -
516 - public function getLoaderScript() {
517 - if ( count( $this->loaders ) == 0 ) {
518 - return false;
519 - }
520 -
521 - return self::concatScripts( $this->loaders );
522 - }
523 -
524 - /**
525 - * Get the last modified timestamp of this module, which is calculated
526 - * as the highest last modified timestamp of its constituent files and
527 - * the files it depends on (see getFileDependencies()). Only files
528 - * relevant to the given language and skin are taken into account, and
529 - * files only relevant in debug mode are not taken into account when
530 - * debug mode is off.
531 - *
532 - * @param $context ResourceLoaderContext object
533 - * @return Integer: UNIX timestamp
534 - */
535 - public function getModifiedTime( ResourceLoaderContext $context ) {
536 - if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
537 - return $this->modifiedTime[$context->getHash()];
538 - }
539 - wfProfileIn( __METHOD__ );
540 -
541 - // Sort of nasty way we can get a flat list of files depended on by all styles
542 - $styles = array();
543 - foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
544 - $styles = array_merge( $styles, $styleFiles );
545 - }
546 - $skinFiles = (array) self::getSkinFiles(
547 - $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' )
548 - );
549 - foreach ( $skinFiles as $styleFiles ) {
550 - $styles = array_merge( $styles, $styleFiles );
551 - }
552 -
553 - // Final merge, this should result in a master list of dependent files
554 - $files = array_merge(
555 - $this->scripts,
556 - $styles,
557 - $context->getDebug() ? $this->debugScripts : array(),
558 - isset( $this->languageScripts[$context->getLanguage()] ) ?
559 - (array) $this->languageScripts[$context->getLanguage()] : array(),
560 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
561 - $this->loaders,
562 - $this->getFileDependencies( $context->getSkin() )
563 - );
564 -
565 - wfProfileIn( __METHOD__.'-filemtime' );
566 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
567 - wfProfileOut( __METHOD__.'-filemtime' );
568 - $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
569 - wfProfileOut( __METHOD__ );
570 - return $this->modifiedTime[$context->getHash()];
571 - }
572 -
573 - /* Protected Members */
574 -
575 - /**
576 - * Get the primary JS for this module. This is pulled from the
577 - * script files added through addScripts()
578 - *
579 - * @return String: JS
580 - */
581 - protected function getPrimaryScript() {
582 - return self::concatScripts( $this->scripts );
583 - }
584 -
585 - /**
586 - * Get the primary CSS for this module. This is pulled from the CSS
587 - * files added through addStyles()
588 - *
589 - * @return Array
590 - */
591 - protected function getPrimaryStyles() {
592 - return self::concatStyles( $this->styles );
593 - }
594 -
595 - /**
596 - * Get the debug JS for this module. This is pulled from the script
597 - * files added through addDebugScripts()
598 - *
599 - * @return String: JS
600 - */
601 - protected function getDebugScript() {
602 - return self::concatScripts( $this->debugScripts );
603 - }
604 -
605 - /**
606 - * Get the language-specific JS for a given language. This is pulled
607 - * from the language-specific script files added through addLanguageScripts()
608 - *
609 - * @return String: JS
610 - */
611 - protected function getLanguageScript( $lang ) {
612 - if ( !isset( $this->languageScripts[$lang] ) ) {
613 - return '';
614 - }
615 - return self::concatScripts( $this->languageScripts[$lang] );
616 - }
617 -
618 - /**
619 - * Get the skin-specific JS for a given skin. This is pulled from the
620 - * skin-specific JS files added through addSkinScripts()
621 - *
622 - * @return String: JS
623 - */
624 - protected function getSkinScript( $skin ) {
625 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
626 - }
627 -
628 - /**
629 - * Get the skin-specific CSS for a given skin. This is pulled from the
630 - * skin-specific CSS files added through addSkinStyles()
631 - *
632 - * @return Array: list of CSS strings keyed by media type
633 - */
634 - protected function getSkinStyles( $skin ) {
635 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
636 - }
637 -
638 - /**
639 - * Helper function to get skin-specific data from an array.
640 - *
641 - * @param $skin String: skin name
642 - * @param $map Array: map of skin names to arrays
643 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
644 - */
645 - protected static function getSkinFiles( $skin, $map ) {
646 - $retval = array();
647 -
648 - if ( isset( $map[$skin] ) && $map[$skin] ) {
649 - $retval = $map[$skin];
650 - } else if ( isset( $map['default'] ) ) {
651 - $retval = $map['default'];
652 - }
653 -
654 - return $retval;
655 - }
656 -
657 - /**
658 - * Get the contents of a set of files and concatenate them, with
659 - * newlines in between. Each file is used only once.
660 - *
661 - * @param $files Array of file names
662 - * @return String: concatenated contents of $files
663 - */
664 - protected static function concatScripts( $files ) {
665 - return implode( "\n",
666 - array_map(
667 - 'file_get_contents',
668 - array_map(
669 - array( __CLASS__, 'remapFilename' ),
670 - array_unique( (array) $files ) ) ) );
671 - }
672 -
673 - protected static function organizeFilesByOption( $files, $option, $default ) {
674 - $organizedFiles = array();
675 - foreach ( (array) $files as $key => $value ) {
676 - if ( is_int( $key ) ) {
677 - // File name as the value
678 - if ( !isset( $organizedFiles[$default] ) ) {
679 - $organizedFiles[$default] = array();
680 - }
681 - $organizedFiles[$default][] = $value;
682 - } else if ( is_array( $value ) ) {
683 - // File name as the key, options array as the value
684 - $media = isset( $value[$option] ) ? $value[$option] : $default;
685 - if ( !isset( $organizedFiles[$media] ) ) {
686 - $organizedFiles[$media] = array();
687 - }
688 - $organizedFiles[$media][] = $key;
689 - }
690 - }
691 - return $organizedFiles;
692 - }
693 -
694 - /**
695 - * Get the contents of a set of CSS files, remap then and concatenate
696 - * them, with newlines in between. Each file is used only once.
697 - *
698 - * @param $styles Array of file names
699 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
700 - */
701 - protected static function concatStyles( $styles ) {
702 - $styles = self::organizeFilesByOption( $styles, 'media', 'all' );
703 - foreach ( $styles as $media => $files ) {
704 - $styles[$media] =
705 - implode( "\n",
706 - array_map(
707 - array( __CLASS__, 'remapStyle' ),
708 - array_unique( (array) $files ) ) );
709 - }
710 - return $styles;
711 - }
712 -
713 - /**
714 - * Remap a relative to $IP. Used as a callback for array_map()
715 - *
716 - * @param $file String: file name
717 - * @return string $IP/$file
718 - */
719 - protected static function remapFilename( $file ) {
720 - global $IP;
721 -
722 - return "$IP/$file";
723 - }
724 -
725 - /**
726 - * Get the contents of a CSS file and run it through CSSMin::remap().
727 - * This wrapper is needed so we can use array_map() in concatStyles()
728 - *
729 - * @param $file String: file name
730 - * @return string Remapped CSS
731 - */
732 - protected static function remapStyle( $file ) {
733 - global $wgScriptPath;
734 - return CSSMin::remap(
735 - file_get_contents( self::remapFilename( $file ) ),
736 - dirname( $file ),
737 - $wgScriptPath . '/' . dirname( $file ),
738 - true
739 - );
740 - }
741 -}
742 -
743 -/**
744 - * Abstraction for resource loader modules which pull from wiki pages
745 - *
746 - * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
747 - * functionality of Title::isValidCssJsSubpage.
748 - */
749 -abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
750 -
751 - /* Protected Members */
752 -
753 - // In-object cache for modified time
754 - protected $modifiedTime = array();
755 -
756 - /* Abstract Protected Methods */
757 -
758 - abstract protected function getPages( ResourceLoaderContext $context );
759 -
760 - /* Protected Methods */
761 -
762 - protected function getContent( $page, $ns ) {
763 - if ( $ns === NS_MEDIAWIKI ) {
764 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
765 - }
766 - if ( $title = Title::newFromText( $page, $ns ) ) {
767 - if ( $title->isValidCssJsSubpage() && $revision = Revision::newFromTitle( $title ) ) {
768 - return $revision->getRawText();
769 - }
770 - }
771 - return null;
772 - }
773 -
774 - /* Methods */
775 -
776 - public function getScript( ResourceLoaderContext $context ) {
777 - $scripts = '';
778 - foreach ( $this->getPages( $context ) as $page => $options ) {
779 - if ( $options['type'] === 'script' ) {
780 - if ( $script = $this->getContent( $page, $options['ns'] ) ) {
781 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
782 - $scripts .= "/*$ns:$page */\n$script\n";
783 - }
784 - }
785 - }
786 - return $scripts;
787 - }
788 -
789 - public function getStyles( ResourceLoaderContext $context ) {
790 -
791 - $styles = array();
792 - foreach ( $this->getPages( $context ) as $page => $options ) {
793 - if ( $options['type'] === 'style' ) {
794 - $media = isset( $options['media'] ) ? $options['media'] : 'all';
795 - if ( $style = $this->getContent( $page, $options['ns'] ) ) {
796 - if ( !isset( $styles[$media] ) ) {
797 - $styles[$media] = '';
798 - }
799 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
800 - $styles[$media] .= "/* $ns:$page */\n$style\n";
801 - }
802 - }
803 - }
804 - return $styles;
805 - }
806 -
807 - public function getModifiedTime( ResourceLoaderContext $context ) {
808 - $hash = $context->getHash();
809 - if ( isset( $this->modifiedTime[$hash] ) ) {
810 - return $this->modifiedTime[$hash];
811 - }
812 -
813 - $titles = array();
814 - foreach ( $this->getPages( $context ) as $page => $options ) {
815 - $titles[$options['ns']][$page] = true;
816 - }
817 -
818 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
819 -
820 - if ( $titles ) {
821 - $dbr = wfGetDB( DB_SLAVE );
822 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
823 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
824 - __METHOD__ );
825 -
826 - if ( $latest ) {
827 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
828 - }
829 - }
830 -
831 - return $this->modifiedTime[$hash] = $modifiedTime;
832 - }
833 -}
834 -
835 -/**
836 - * Module for site customizations
837 - */
838 -class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
839 -
840 - /* Protected Methods */
841 -
842 - protected function getPages( ResourceLoaderContext $context ) {
843 - global $wgHandheldStyle;
844 -
845 - $pages = array(
846 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
847 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
848 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
849 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
850 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
851 - );
852 - if ( $wgHandheldStyle ) {
853 - $pages['Handheld.css'] = array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'handheld' );
854 - }
855 - return $pages;
856 - }
857 -
858 - /* Methods */
859 -
860 - public function getGroup() {
861 - return 'site';
862 - }
863 -}
864 -
865 -/**
866 - * Module for user customizations
867 - */
868 -class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
869 -
870 - /* Protected Methods */
871 -
872 - protected function getPages( ResourceLoaderContext $context ) {
873 - global $wgAllowUserCss;
874 -
875 - if ( $context->getUser() && $wgAllowUserCss ) {
876 - $username = $context->getUser();
877 - return array(
878 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
879 - "$username/" . $context->getSkin() . '.js' => array( 'ns' => NS_USER, 'type' => 'script' ),
880 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
881 - "$username/" . $context->getSkin() . '.css' => array( 'ns' => NS_USER, 'type' => 'style' ),
882 - );
883 - }
884 - return array();
885 - }
886 -
887 - /* Methods */
888 -
889 - public function getGroup() {
890 - return 'user';
891 - }
892 -}
893 -
894 -/**
895 - * Module for user preference customizations
896 - */
897 -class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
898 -
899 - /* Protected Members */
900 -
901 - protected $modifiedTime = array();
902 -
903 - /* Methods */
904 -
905 - public function getModifiedTime( ResourceLoaderContext $context ) {
906 - $hash = $context->getHash();
907 - if ( isset( $this->modifiedTime[$hash] ) ) {
908 - return $this->modifiedTime[$hash];
909 - }
910 -
911 - global $wgUser;
912 -
913 - if ( $context->getUser() === $wgUser->getName() ) {
914 - return $this->modifiedTime[$hash] = $wgUser->getTouched();
915 - } else {
916 - return 1;
917 - }
918 - }
919 -
920 - /**
921 - * Fetch the context's user options, or if it doesn't match current user,
922 - * the default options.
923 - *
924 - * @param $context ResourceLoaderContext
925 - * @return array
926 - */
927 - protected function contextUserOptions( ResourceLoaderContext $context ) {
928 - global $wgUser;
929 -
930 - // Verify identity -- this is a private module
931 - if ( $context->getUser() === $wgUser->getName() ) {
932 - return $wgUser->getOptions();
933 - } else {
934 - return User::getDefaultOptions();
935 - }
936 - }
937 -
938 - public function getScript( ResourceLoaderContext $context ) {
939 - $encOptions = FormatJson::encode( $this->contextUserOptions( $context ) );
940 - return "mediaWiki.user.options.set( $encOptions );";
941 - }
942 -
943 - public function getStyles( ResourceLoaderContext $context ) {
944 - global $wgAllowUserCssPrefs;
945 -
946 - if ( $wgAllowUserCssPrefs ) {
947 - $options = $this->contextUserOptions( $context );
948 -
949 - // Build CSS rules
950 - $rules = array();
951 - if ( $options['underline'] < 2 ) {
952 - $rules[] = "a { text-decoration: " . ( $options['underline'] ? 'underline' : 'none' ) . "; }";
953 - }
954 - if ( $options['highlightbroken'] ) {
955 - $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
956 - } else {
957 - $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
958 - $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
959 - $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
960 - }
961 - if ( $options['justify'] ) {
962 - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
963 - }
964 - if ( !$options['showtoc'] ) {
965 - $rules[] = "#toc { display: none; }\n";
966 - }
967 - if ( !$options['editsection'] ) {
968 - $rules[] = ".editsection { display: none; }\n";
969 - }
970 - if ( $options['editfont'] !== 'default' ) {
971 - $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
972 - }
973 - return array( 'all' => implode( "\n", $rules ) );
974 - }
975 - return array();
976 - }
977 -
978 - public function getFlip( $context ) {
979 - global $wgContLang;
980 -
981 - return $wgContLang->getDir() !== $context->getDirection();
982 - }
983 -
984 - public function getGroup() {
985 - return 'private';
986 - }
987 -}
988 -
989 -class ResourceLoaderStartUpModule extends ResourceLoaderModule {
990 - /* Protected Members */
991 -
992 - protected $modifiedTime = array();
993 -
994 - /* Protected Methods */
995 -
996 - protected function getConfig( $context ) {
997 - global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
998 - $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgBreakFrames,
999 - $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
1000 - $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
1001 - $wgSitename, $wgFileExtensions;
1002 -
1003 - // Pre-process information
1004 - $separatorTransTable = $wgContLang->separatorTransformTable();
1005 - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
1006 - $compactSeparatorTransTable = array(
1007 - implode( "\t", array_keys( $separatorTransTable ) ),
1008 - implode( "\t", $separatorTransTable ),
1009 - );
1010 - $digitTransTable = $wgContLang->digitTransformTable();
1011 - $digitTransTable = $digitTransTable ? $digitTransTable : array();
1012 - $compactDigitTransTable = array(
1013 - implode( "\t", array_keys( $digitTransTable ) ),
1014 - implode( "\t", $digitTransTable ),
1015 - );
1016 - $mainPage = Title::newMainPage();
1017 -
1018 - // Build list of variables
1019 - $vars = array(
1020 - 'wgLoadScript' => $wgLoadScript,
1021 - 'debug' => $context->getDebug(),
1022 - 'skin' => $context->getSkin(),
1023 - 'stylepath' => $wgStylePath,
1024 - 'wgUrlProtocols' => wfUrlProtocols(),
1025 - 'wgArticlePath' => $wgArticlePath,
1026 - 'wgScriptPath' => $wgScriptPath,
1027 - 'wgScriptExtension' => $wgScriptExtension,
1028 - 'wgScript' => $wgScript,
1029 - 'wgVariantArticlePath' => $wgVariantArticlePath,
1030 - 'wgActionPaths' => $wgActionPaths,
1031 - 'wgServer' => $wgServer,
1032 - 'wgUserLanguage' => $context->getLanguage(),
1033 - 'wgContentLanguage' => $wgContLang->getCode(),
1034 - 'wgBreakFrames' => $wgBreakFrames,
1035 - 'wgVersion' => $wgVersion,
1036 - 'wgEnableAPI' => $wgEnableAPI,
1037 - 'wgEnableWriteAPI' => $wgEnableWriteAPI,
1038 - 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
1039 - 'wgDigitTransformTable' => $compactDigitTransTable,
1040 - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
1041 - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
1042 - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
1043 - 'wgSiteName' => $wgSitename,
1044 - 'wgFileExtensions' => $wgFileExtensions,
1045 - 'wgDBname' => $wgDBname,
1046 - );
1047 - if ( $wgContLang->hasVariants() ) {
1048 - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
1049 - }
1050 - if ( $wgUseAjax && $wgEnableMWSuggest ) {
1051 - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
1052 - }
1053 -
1054 - return $vars;
1055 - }
1056 -
1057 - /**
1058 - * Gets registration code for all modules
1059 - *
1060 - * @param $context ResourceLoaderContext object
1061 - * @return String: JavaScript code for registering all modules with the client loader
1062 - */
1063 - public static function getModuleRegistrations( ResourceLoaderContext $context ) {
1064 - global $wgCacheEpoch;
1065 - wfProfileIn( __METHOD__ );
1066 -
1067 - $out = '';
1068 - $registrations = array();
1069 - foreach ( $context->getResourceLoader()->getModules() as $name => $module ) {
1070 - // Support module loader scripts
1071 - if ( ( $loader = $module->getLoaderScript() ) !== false ) {
1072 - $deps = $module->getDependencies();
1073 - $group = $module->getGroup();
1074 - $version = wfTimestamp( TS_ISO_8601_BASIC, round( $module->getModifiedTime( $context ), -2 ) );
1075 - $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
1076 - }
1077 - // Automatically register module
1078 - else {
1079 - $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
1080 - // Modules without dependencies or a group pass two arguments (name, timestamp) to
1081 - // mediaWiki.loader.register()
1082 - if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
1083 - $registrations[] = array( $name, $mtime );
1084 - }
1085 - // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies)
1086 - // to mediaWiki.loader.register()
1087 - else if ( $module->getGroup() === null ) {
1088 - $registrations[] = array(
1089 - $name, $mtime, $module->getDependencies() );
1090 - }
1091 - // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
1092 - // to mediaWiki.loader.register()
1093 - else {
1094 - $registrations[] = array(
1095 - $name, $mtime, $module->getDependencies(), $module->getGroup() );
1096 - }
1097 - }
1098 - }
1099 - $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
1100 -
1101 - wfProfileOut( __METHOD__ );
1102 - return $out;
1103 - }
1104 -
1105 - /* Methods */
1106 -
1107 - public function getScript( ResourceLoaderContext $context ) {
1108 - global $IP, $wgLoadScript;
1109 -
1110 - $out = file_get_contents( "$IP/resources/startup.js" );
1111 - if ( $context->getOnly() === 'scripts' ) {
1112 - // Build load query for jquery and mediawiki modules
1113 - $query = array(
1114 - 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
1115 - 'only' => 'scripts',
1116 - 'lang' => $context->getLanguage(),
1117 - 'skin' => $context->getSkin(),
1118 - 'debug' => $context->getDebug() ? 'true' : 'false',
1119 - 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
1120 - $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
1121 - $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
1122 - ), -2 ) )
1123 - );
1124 - // Ensure uniform query order
1125 - ksort( $query );
1126 -
1127 - // Startup function
1128 - $configuration = FormatJson::encode( $this->getConfig( $context ) );
1129 - $registrations = self::getModuleRegistrations( $context );
1130 - $out .= "var startUp = function() {\n\t$registrations\n\tmediaWiki.config.set( $configuration );\n};";
1131 -
1132 - // Conditional script injection
1133 - $scriptTag = Xml::escapeJsString( Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) );
1134 - $out .= "if ( isCompatible() ) {\n\tdocument.write( '$scriptTag' );\n}\ndelete isCompatible;";
1135 - }
1136 -
1137 - return $out;
1138 - }
1139 -
1140 - public function getModifiedTime( ResourceLoaderContext $context ) {
1141 - global $IP, $wgCacheEpoch;
1142 -
1143 - $hash = $context->getHash();
1144 - if ( isset( $this->modifiedTime[$hash] ) ) {
1145 - return $this->modifiedTime[$hash];
1146 - }
1147 - $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
1148 -
1149 - // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
1150 - // before making changes to this code!
1151 - $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
1152 - foreach ( $context->getResourceLoader()->getModules() as $module ) {
1153 - $time = max( $time, $module->getModifiedTime( $context ) );
1154 - }
1155 - return $this->modifiedTime[$hash] = $time;
1156 - }
1157 -
1158 - public function getFlip( $context ) {
1159 - global $wgContLang;
1160 -
1161 - return $wgContLang->getDir() !== $context->getDirection();
1162 - }
1163 -
1164 - /* Methods */
1165 -
1166 - public function getGroup() {
1167 - return 'startup';
1168 - }
1169 -}
Index: trunk/phase3/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -22,969 +22,6 @@
2323
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
26 -/**
27 - * Abstraction for resource loader modules, with name registration and maxage functionality.
28 - */
29 -abstract class ResourceLoaderModule {
30 -
31 - /* Protected Members */
32 -
33 - protected $name = null;
34 -
35 - // In-object cache for file dependencies
36 - protected $fileDeps = array();
37 - // In-object cache for message blob mtime
38 - protected $msgBlobMtime = array();
39 -
40 - /* Methods */
41 -
42 - /**
43 - * Get this module's name. This is set when the module is registered
44 - * with ResourceLoader::register()
45 - *
46 - * @return Mixed: name (string) or null if no name was set
47 - */
48 - public function getName() {
49 - return $this->name;
50 - }
51 -
52 - /**
53 - * Set this module's name. This is called by ResourceLodaer::register()
54 - * when registering the module. Other code should not call this.
55 - *
56 - * @param $name String: name
57 - */
58 - public function setName( $name ) {
59 - $this->name = $name;
60 - }
61 -
62 - /**
63 - * Get whether CSS for this module should be flipped
64 - */
65 - public function getFlip( $context ) {
66 - return $context->getDirection() === 'rtl';
67 - }
68 -
69 - /**
70 - * Get all JS for this module for a given language and skin.
71 - * Includes all relevant JS except loader scripts.
72 - *
73 - * @param $context ResourceLoaderContext object
74 - * @return String: JS
75 - */
76 - public function getScript( ResourceLoaderContext $context ) {
77 - // Stub, override expected
78 - return '';
79 - }
80 -
81 - /**
82 - * Get all CSS for this module for a given skin.
83 - *
84 - * @param $context ResourceLoaderContext object
85 - * @return array: strings of CSS keyed by media type
86 - */
87 - public function getStyles( ResourceLoaderContext $context ) {
88 - // Stub, override expected
89 - return '';
90 - }
91 -
92 - /**
93 - * Get the messages needed for this module.
94 - *
95 - * To get a JSON blob with messages, use MessageBlobStore::get()
96 - *
97 - * @return array of message keys. Keys may occur more than once
98 - */
99 - public function getMessages() {
100 - // Stub, override expected
101 - return array();
102 - }
103 -
104 - /**
105 - * Get the group this module is in.
106 - *
107 - * @return string of group name
108 - */
109 - public function getGroup() {
110 - // Stub, override expected
111 - return null;
112 - }
113 -
114 - /**
115 - * Get the loader JS for this module, if set.
116 - *
117 - * @return Mixed: loader JS (string) or false if no custom loader set
118 - */
119 - public function getLoaderScript() {
120 - // Stub, override expected
121 - return false;
122 - }
123 -
124 - /**
125 - * Get a list of modules this module depends on.
126 - *
127 - * Dependency information is taken into account when loading a module
128 - * on the client side. When adding a module on the server side,
129 - * dependency information is NOT taken into account and YOU are
130 - * responsible for adding dependent modules as well. If you don't do
131 - * this, the client side loader will send a second request back to the
132 - * server to fetch the missing modules, which kind of defeats the
133 - * purpose of the resource loader.
134 - *
135 - * To add dependencies dynamically on the client side, use a custom
136 - * loader script, see getLoaderScript()
137 - * @return Array of module names (strings)
138 - */
139 - public function getDependencies() {
140 - // Stub, override expected
141 - return array();
142 - }
143 -
144 - /**
145 - * Get the files this module depends on indirectly for a given skin.
146 - * Currently these are only image files referenced by the module's CSS.
147 - *
148 - * @param $skin String: skin name
149 - * @return array of files
150 - */
151 - public function getFileDependencies( $skin ) {
152 - // Try in-object cache first
153 - if ( isset( $this->fileDeps[$skin] ) ) {
154 - return $this->fileDeps[$skin];
155 - }
156 -
157 - $dbr = wfGetDB( DB_SLAVE );
158 - $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
159 - 'md_module' => $this->getName(),
160 - 'md_skin' => $skin,
161 - ), __METHOD__
162 - );
163 - if ( !is_null( $deps ) ) {
164 - return $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
165 - }
166 - return $this->fileDeps[$skin] = array();
167 - }
168 -
169 - /**
170 - * Set preloaded file dependency information. Used so we can load this
171 - * information for all modules at once.
172 - * @param $skin string Skin name
173 - * @param $deps array Array of file names
174 - */
175 - public function setFileDependencies( $skin, $deps ) {
176 - $this->fileDeps[$skin] = $deps;
177 - }
178 -
179 - /**
180 - * Get the last modification timestamp of the message blob for this
181 - * module in a given language.
182 - * @param $lang string Language code
183 - * @return int UNIX timestamp, or 0 if no blob found
184 - */
185 - public function getMsgBlobMtime( $lang ) {
186 - if ( !count( $this->getMessages() ) )
187 - return 0;
188 -
189 - $dbr = wfGetDB( DB_SLAVE );
190 - $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
191 - 'mr_resource' => $this->getName(),
192 - 'mr_lang' => $lang
193 - ), __METHOD__
194 - );
195 - $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
196 - return $this->msgBlobMtime[$lang];
197 - }
198 -
199 - /**
200 - * Set a preloaded message blob last modification timestamp. Used so we
201 - * can load this information for all modules at once.
202 - * @param $lang string Language code
203 - * @param $mtime int UNIX timestamp or 0 if there is no such blob
204 - */
205 - public function setMsgBlobMtime( $lang, $mtime ) {
206 - $this->msgBlobMtime[$lang] = $mtime;
207 - }
208 -
209 - /* Abstract Methods */
210 -
211 - /**
212 - * Get this module's last modification timestamp for a given
213 - * combination of language, skin and debug mode flag. This is typically
214 - * the highest of each of the relevant components' modification
215 - * timestamps. Whenever anything happens that changes the module's
216 - * contents for these parameters, the mtime should increase.
217 - *
218 - * @param $context ResourceLoaderContext object
219 - * @return int UNIX timestamp
220 - */
221 - public function getModifiedTime( ResourceLoaderContext $context ) {
222 - // 0 would mean now
223 - return 1;
224 - }
225 -}
226 -
227 -/**
228 - * Module based on local JS/CSS files. This is the most common type of module.
229 - */
230 -class ResourceLoaderFileModule extends ResourceLoaderModule {
231 - /* Protected Members */
232 -
233 - protected $scripts = array();
234 - protected $styles = array();
235 - protected $messages = array();
236 - protected $group;
237 - protected $dependencies = array();
238 - protected $debugScripts = array();
239 - protected $languageScripts = array();
240 - protected $skinScripts = array();
241 - protected $skinStyles = array();
242 - protected $loaders = array();
243 - protected $parameters = array();
244 -
245 - // In-object cache for file dependencies
246 - protected $fileDeps = array();
247 - // In-object cache for mtime
248 - protected $modifiedTime = array();
249 -
250 - /* Methods */
251 -
252 - /**
253 - * Construct a new module from an options array.
254 - *
255 - * @param $options array Options array. If empty, an empty module will be constructed
256 - *
257 - * $options format:
258 - * array(
259 - * // Required module options (mutually exclusive)
260 - * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ),
261 - *
262 - * // Optional module options
263 - * 'languageScripts' => array(
264 - * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... )
265 - * ...
266 - * ),
267 - * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ),
268 - * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ),
269 - *
270 - * // Non-raw module options
271 - * 'dependencies' => 'module' | array( 'module1', 'module2' ... )
272 - * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ),
273 - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), |
274 - * array( 'dir/file1.css' => array( 'media' => 'print' ) ),
275 - * 'skinStyles' => array(
276 - * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) |
277 - * array( 'dir/file1.css' => array( 'media' => 'print' )
278 - * ...
279 - * ),
280 - * 'messages' => array( 'message1', 'message2' ... ),
281 - * 'group' => 'stuff',
282 - * )
283 - *
284 - * @param $basePath String: base path to prepend to all paths in $options
285 - */
286 - public function __construct( $options = array(), $basePath = null ) {
287 - foreach ( $options as $option => $value ) {
288 - switch ( $option ) {
289 - case 'scripts':
290 - case 'debugScripts':
291 - case 'languageScripts':
292 - case 'skinScripts':
293 - case 'loaders':
294 - $this->{$option} = (array)$value;
295 - // Automatically prefix script paths
296 - if ( is_string( $basePath ) ) {
297 - foreach ( $this->{$option} as $key => $value ) {
298 - $this->{$option}[$key] = $basePath . $value;
299 - }
300 - }
301 - break;
302 - case 'styles':
303 - case 'skinStyles':
304 - $this->{$option} = (array)$value;
305 - // Automatically prefix style paths
306 - if ( is_string( $basePath ) ) {
307 - foreach ( $this->{$option} as $key => $value ) {
308 - if ( is_array( $value ) ) {
309 - $this->{$option}[$basePath . $key] = $value;
310 - unset( $this->{$option}[$key] );
311 - } else {
312 - $this->{$option}[$key] = $basePath . $value;
313 - }
314 - }
315 - }
316 - break;
317 - case 'dependencies':
318 - case 'messages':
319 - $this->{$option} = (array)$value;
320 - break;
321 - case 'group':
322 - $this->group = (string)$value;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - /**
329 - * Add script files to this module. In order to be valid, a module
330 - * must contain at least one script file.
331 - *
332 - * @param $scripts Mixed: path to script file (string) or array of paths
333 - */
334 - public function addScripts( $scripts ) {
335 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
336 - }
337 -
338 - /**
339 - * Add style (CSS) files to this module.
340 - *
341 - * @param $styles Mixed: path to CSS file (string) or array of paths
342 - */
343 - public function addStyles( $styles ) {
344 - $this->styles = array_merge( $this->styles, (array)$styles );
345 - }
346 -
347 - /**
348 - * Add messages to this module.
349 - *
350 - * @param $messages Mixed: message key (string) or array of message keys
351 - */
352 - public function addMessages( $messages ) {
353 - $this->messages = array_merge( $this->messages, (array)$messages );
354 - }
355 -
356 - /**
357 - * Sets the group of this module.
358 - *
359 - * @param $group string group name
360 - */
361 - public function setGroup( $group ) {
362 - $this->group = $group;
363 - }
364 -
365 - /**
366 - * Add dependencies. Dependency information is taken into account when
367 - * loading a module on the client side. When adding a module on the
368 - * server side, dependency information is NOT taken into account and
369 - * YOU are responsible for adding dependent modules as well. If you
370 - * don't do this, the client side loader will send a second request
371 - * back to the server to fetch the missing modules, which kind of
372 - * defeats the point of using the resource loader in the first place.
373 - *
374 - * To add dependencies dynamically on the client side, use a custom
375 - * loader (see addLoaders())
376 - *
377 - * @param $dependencies Mixed: module name (string) or array of module names
378 - */
379 - public function addDependencies( $dependencies ) {
380 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
381 - }
382 -
383 - /**
384 - * Add debug scripts to the module. These scripts are only included
385 - * in debug mode.
386 - *
387 - * @param $scripts Mixed: path to script file (string) or array of paths
388 - */
389 - public function addDebugScripts( $scripts ) {
390 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
391 - }
392 -
393 - /**
394 - * Add language-specific scripts. These scripts are only included for
395 - * a given language.
396 - *
397 - * @param $lang String: language code
398 - * @param $scripts Mixed: path to script file (string) or array of paths
399 - */
400 - public function addLanguageScripts( $lang, $scripts ) {
401 - $this->languageScripts = array_merge_recursive(
402 - $this->languageScripts,
403 - array( $lang => $scripts )
404 - );
405 - }
406 -
407 - /**
408 - * Add skin-specific scripts. These scripts are only included for
409 - * a given skin.
410 - *
411 - * @param $skin String: skin name, or 'default'
412 - * @param $scripts Mixed: path to script file (string) or array of paths
413 - */
414 - public function addSkinScripts( $skin, $scripts ) {
415 - $this->skinScripts = array_merge_recursive(
416 - $this->skinScripts,
417 - array( $skin => $scripts )
418 - );
419 - }
420 -
421 - /**
422 - * Add skin-specific CSS. These CSS files are only included for a
423 - * given skin. If there are no skin-specific CSS files for a skin,
424 - * the files defined for 'default' will be used, if any.
425 - *
426 - * @param $skin String: skin name, or 'default'
427 - * @param $scripts Mixed: path to CSS file (string) or array of paths
428 - */
429 - public function addSkinStyles( $skin, $scripts ) {
430 - $this->skinStyles = array_merge_recursive(
431 - $this->skinStyles,
432 - array( $skin => $scripts )
433 - );
434 - }
435 -
436 - /**
437 - * Add loader scripts. These scripts are loaded on every page and are
438 - * responsible for registering this module using
439 - * mediaWiki.loader.register(). If there are no loader scripts defined,
440 - * the resource loader will register the module itself.
441 - *
442 - * Loader scripts are used to determine a module's dependencies
443 - * dynamically on the client side (e.g. based on browser type/version).
444 - * Note that loader scripts are included on every page, so they should
445 - * be lightweight and use mediaWiki.loader.register()'s callback
446 - * feature to defer dependency calculation.
447 - *
448 - * @param $scripts Mixed: path to script file (string) or array of paths
449 - */
450 - public function addLoaders( $scripts ) {
451 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
452 - }
453 -
454 - public function getScript( ResourceLoaderContext $context ) {
455 - $retval = $this->getPrimaryScript() . "\n" .
456 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
457 - $this->getSkinScript( $context->getSkin() );
458 -
459 - if ( $context->getDebug() ) {
460 - $retval .= $this->getDebugScript();
461 - }
462 -
463 - return $retval;
464 - }
465 -
466 - public function getStyles( ResourceLoaderContext $context ) {
467 - $styles = array();
468 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
469 - if ( !isset( $styles[$media] ) ) {
470 - $styles[$media] = '';
471 - }
472 - $styles[$media] .= $style;
473 - }
474 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
475 - if ( !isset( $styles[$media] ) ) {
476 - $styles[$media] = '';
477 - }
478 - $styles[$media] .= $style;
479 - }
480 -
481 - // Collect referenced files
482 - $files = array();
483 - foreach ( $styles as $style ) {
484 - // Extract and store the list of referenced files
485 - $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
486 - }
487 -
488 - // Only store if modified
489 - if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
490 - $encFiles = FormatJson::encode( $files );
491 - $dbw = wfGetDB( DB_MASTER );
492 - $dbw->replace( 'module_deps',
493 - array( array( 'md_module', 'md_skin' ) ), array(
494 - 'md_module' => $this->getName(),
495 - 'md_skin' => $context->getSkin(),
496 - 'md_deps' => $encFiles,
497 - )
498 - );
499 - }
500 -
501 - return $styles;
502 - }
503 -
504 - public function getMessages() {
505 - return $this->messages;
506 - }
507 -
508 - public function getGroup() {
509 - return $this->group;
510 - }
511 -
512 - public function getDependencies() {
513 - return $this->dependencies;
514 - }
515 -
516 - public function getLoaderScript() {
517 - if ( count( $this->loaders ) == 0 ) {
518 - return false;
519 - }
520 -
521 - return self::concatScripts( $this->loaders );
522 - }
523 -
524 - /**
525 - * Get the last modified timestamp of this module, which is calculated
526 - * as the highest last modified timestamp of its constituent files and
527 - * the files it depends on (see getFileDependencies()). Only files
528 - * relevant to the given language and skin are taken into account, and
529 - * files only relevant in debug mode are not taken into account when
530 - * debug mode is off.
531 - *
532 - * @param $context ResourceLoaderContext object
533 - * @return Integer: UNIX timestamp
534 - */
535 - public function getModifiedTime( ResourceLoaderContext $context ) {
536 - if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
537 - return $this->modifiedTime[$context->getHash()];
538 - }
539 - wfProfileIn( __METHOD__ );
540 -
541 - // Sort of nasty way we can get a flat list of files depended on by all styles
542 - $styles = array();
543 - foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
544 - $styles = array_merge( $styles, $styleFiles );
545 - }
546 - $skinFiles = (array) self::getSkinFiles(
547 - $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' )
548 - );
549 - foreach ( $skinFiles as $styleFiles ) {
550 - $styles = array_merge( $styles, $styleFiles );
551 - }
552 -
553 - // Final merge, this should result in a master list of dependent files
554 - $files = array_merge(
555 - $this->scripts,
556 - $styles,
557 - $context->getDebug() ? $this->debugScripts : array(),
558 - isset( $this->languageScripts[$context->getLanguage()] ) ?
559 - (array) $this->languageScripts[$context->getLanguage()] : array(),
560 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
561 - $this->loaders,
562 - $this->getFileDependencies( $context->getSkin() )
563 - );
564 -
565 - wfProfileIn( __METHOD__.'-filemtime' );
566 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
567 - wfProfileOut( __METHOD__.'-filemtime' );
568 - $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
569 - wfProfileOut( __METHOD__ );
570 - return $this->modifiedTime[$context->getHash()];
571 - }
572 -
573 - /* Protected Members */
574 -
575 - /**
576 - * Get the primary JS for this module. This is pulled from the
577 - * script files added through addScripts()
578 - *
579 - * @return String: JS
580 - */
581 - protected function getPrimaryScript() {
582 - return self::concatScripts( $this->scripts );
583 - }
584 -
585 - /**
586 - * Get the primary CSS for this module. This is pulled from the CSS
587 - * files added through addStyles()
588 - *
589 - * @return Array
590 - */
591 - protected function getPrimaryStyles() {
592 - return self::concatStyles( $this->styles );
593 - }
594 -
595 - /**
596 - * Get the debug JS for this module. This is pulled from the script
597 - * files added through addDebugScripts()
598 - *
599 - * @return String: JS
600 - */
601 - protected function getDebugScript() {
602 - return self::concatScripts( $this->debugScripts );
603 - }
604 -
605 - /**
606 - * Get the language-specific JS for a given language. This is pulled
607 - * from the language-specific script files added through addLanguageScripts()
608 - *
609 - * @return String: JS
610 - */
611 - protected function getLanguageScript( $lang ) {
612 - if ( !isset( $this->languageScripts[$lang] ) ) {
613 - return '';
614 - }
615 - return self::concatScripts( $this->languageScripts[$lang] );
616 - }
617 -
618 - /**
619 - * Get the skin-specific JS for a given skin. This is pulled from the
620 - * skin-specific JS files added through addSkinScripts()
621 - *
622 - * @return String: JS
623 - */
624 - protected function getSkinScript( $skin ) {
625 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
626 - }
627 -
628 - /**
629 - * Get the skin-specific CSS for a given skin. This is pulled from the
630 - * skin-specific CSS files added through addSkinStyles()
631 - *
632 - * @return Array: list of CSS strings keyed by media type
633 - */
634 - protected function getSkinStyles( $skin ) {
635 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
636 - }
637 -
638 - /**
639 - * Helper function to get skin-specific data from an array.
640 - *
641 - * @param $skin String: skin name
642 - * @param $map Array: map of skin names to arrays
643 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
644 - */
645 - protected static function getSkinFiles( $skin, $map ) {
646 - $retval = array();
647 -
648 - if ( isset( $map[$skin] ) && $map[$skin] ) {
649 - $retval = $map[$skin];
650 - } else if ( isset( $map['default'] ) ) {
651 - $retval = $map['default'];
652 - }
653 -
654 - return $retval;
655 - }
656 -
657 - /**
658 - * Get the contents of a set of files and concatenate them, with
659 - * newlines in between. Each file is used only once.
660 - *
661 - * @param $files Array of file names
662 - * @return String: concatenated contents of $files
663 - */
664 - protected static function concatScripts( $files ) {
665 - return implode( "\n",
666 - array_map(
667 - 'file_get_contents',
668 - array_map(
669 - array( __CLASS__, 'remapFilename' ),
670 - array_unique( (array) $files ) ) ) );
671 - }
672 -
673 - protected static function organizeFilesByOption( $files, $option, $default ) {
674 - $organizedFiles = array();
675 - foreach ( (array) $files as $key => $value ) {
676 - if ( is_int( $key ) ) {
677 - // File name as the value
678 - if ( !isset( $organizedFiles[$default] ) ) {
679 - $organizedFiles[$default] = array();
680 - }
681 - $organizedFiles[$default][] = $value;
682 - } else if ( is_array( $value ) ) {
683 - // File name as the key, options array as the value
684 - $media = isset( $value[$option] ) ? $value[$option] : $default;
685 - if ( !isset( $organizedFiles[$media] ) ) {
686 - $organizedFiles[$media] = array();
687 - }
688 - $organizedFiles[$media][] = $key;
689 - }
690 - }
691 - return $organizedFiles;
692 - }
693 -
694 - /**
695 - * Get the contents of a set of CSS files, remap then and concatenate
696 - * them, with newlines in between. Each file is used only once.
697 - *
698 - * @param $styles Array of file names
699 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
700 - */
701 - protected static function concatStyles( $styles ) {
702 - $styles = self::organizeFilesByOption( $styles, 'media', 'all' );
703 - foreach ( $styles as $media => $files ) {
704 - $styles[$media] =
705 - implode( "\n",
706 - array_map(
707 - array( __CLASS__, 'remapStyle' ),
708 - array_unique( (array) $files ) ) );
709 - }
710 - return $styles;
711 - }
712 -
713 - /**
714 - * Remap a relative to $IP. Used as a callback for array_map()
715 - *
716 - * @param $file String: file name
717 - * @return string $IP/$file
718 - */
719 - protected static function remapFilename( $file ) {
720 - global $IP;
721 -
722 - return "$IP/$file";
723 - }
724 -
725 - /**
726 - * Get the contents of a CSS file and run it through CSSMin::remap().
727 - * This wrapper is needed so we can use array_map() in concatStyles()
728 - *
729 - * @param $file String: file name
730 - * @return string Remapped CSS
731 - */
732 - protected static function remapStyle( $file ) {
733 - global $wgScriptPath;
734 - return CSSMin::remap(
735 - file_get_contents( self::remapFilename( $file ) ),
736 - dirname( $file ),
737 - $wgScriptPath . '/' . dirname( $file ),
738 - true
739 - );
740 - }
741 -}
742 -
743 -/**
744 - * Abstraction for resource loader modules which pull from wiki pages
745 - *
746 - * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
747 - * functionality of Title::isValidCssJsSubpage.
748 - */
749 -abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
750 -
751 - /* Protected Members */
752 -
753 - // In-object cache for modified time
754 - protected $modifiedTime = array();
755 -
756 - /* Abstract Protected Methods */
757 -
758 - abstract protected function getPages( ResourceLoaderContext $context );
759 -
760 - /* Protected Methods */
761 -
762 - protected function getContent( $page, $ns ) {
763 - if ( $ns === NS_MEDIAWIKI ) {
764 - return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page, 'content' );
765 - }
766 - if ( $title = Title::newFromText( $page, $ns ) ) {
767 - if ( $title->isValidCssJsSubpage() && $revision = Revision::newFromTitle( $title ) ) {
768 - return $revision->getRawText();
769 - }
770 - }
771 - return null;
772 - }
773 -
774 - /* Methods */
775 -
776 - public function getScript( ResourceLoaderContext $context ) {
777 - $scripts = '';
778 - foreach ( $this->getPages( $context ) as $page => $options ) {
779 - if ( $options['type'] === 'script' ) {
780 - if ( $script = $this->getContent( $page, $options['ns'] ) ) {
781 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
782 - $scripts .= "/*$ns:$page */\n$script\n";
783 - }
784 - }
785 - }
786 - return $scripts;
787 - }
788 -
789 - public function getStyles( ResourceLoaderContext $context ) {
790 -
791 - $styles = array();
792 - foreach ( $this->getPages( $context ) as $page => $options ) {
793 - if ( $options['type'] === 'style' ) {
794 - $media = isset( $options['media'] ) ? $options['media'] : 'all';
795 - if ( $style = $this->getContent( $page, $options['ns'] ) ) {
796 - if ( !isset( $styles[$media] ) ) {
797 - $styles[$media] = '';
798 - }
799 - $ns = MWNamespace::getCanonicalName( $options['ns'] );
800 - $styles[$media] .= "/* $ns:$page */\n$style\n";
801 - }
802 - }
803 - }
804 - return $styles;
805 - }
806 -
807 - public function getModifiedTime( ResourceLoaderContext $context ) {
808 - $hash = $context->getHash();
809 - if ( isset( $this->modifiedTime[$hash] ) ) {
810 - return $this->modifiedTime[$hash];
811 - }
812 -
813 - $titles = array();
814 - foreach ( $this->getPages( $context ) as $page => $options ) {
815 - $titles[$options['ns']][$page] = true;
816 - }
817 -
818 - $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
819 -
820 - if ( $titles ) {
821 - $dbr = wfGetDB( DB_SLAVE );
822 - $latest = $dbr->selectField( 'page', 'MAX(page_touched)',
823 - $dbr->makeWhereFrom2d( $titles, 'page_namespace', 'page_title' ),
824 - __METHOD__ );
825 -
826 - if ( $latest ) {
827 - $modifiedTime = wfTimestamp( TS_UNIX, $latest );
828 - }
829 - }
830 -
831 - return $this->modifiedTime[$hash] = $modifiedTime;
832 - }
833 -}
834 -
835 -/**
836 - * Module for site customizations
837 - */
838 -class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
839 -
840 - /* Protected Methods */
841 -
842 - protected function getPages( ResourceLoaderContext $context ) {
843 - global $wgHandheldStyle;
844 -
845 - $pages = array(
846 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
847 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
848 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
849 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
850 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
851 - );
852 - if ( $wgHandheldStyle ) {
853 - $pages['Handheld.css'] = array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'handheld' );
854 - }
855 - return $pages;
856 - }
857 -
858 - /* Methods */
859 -
860 - public function getGroup() {
861 - return 'site';
862 - }
863 -}
864 -
865 -/**
866 - * Module for user customizations
867 - */
868 -class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
869 -
870 - /* Protected Methods */
871 -
872 - protected function getPages( ResourceLoaderContext $context ) {
873 - global $wgAllowUserCss;
874 -
875 - if ( $context->getUser() && $wgAllowUserCss ) {
876 - $username = $context->getUser();
877 - return array(
878 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
879 - "$username/" . $context->getSkin() . '.js' => array( 'ns' => NS_USER, 'type' => 'script' ),
880 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
881 - "$username/" . $context->getSkin() . '.css' => array( 'ns' => NS_USER, 'type' => 'style' ),
882 - );
883 - }
884 - return array();
885 - }
886 -
887 - /* Methods */
888 -
889 - public function getGroup() {
890 - return 'user';
891 - }
892 -}
893 -
894 -/**
895 - * Module for user preference customizations
896 - */
897 -class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
898 -
899 - /* Protected Members */
900 -
901 - protected $modifiedTime = array();
902 -
903 - /* Methods */
904 -
905 - public function getModifiedTime( ResourceLoaderContext $context ) {
906 - $hash = $context->getHash();
907 - if ( isset( $this->modifiedTime[$hash] ) ) {
908 - return $this->modifiedTime[$hash];
909 - }
910 -
911 - global $wgUser;
912 -
913 - if ( $context->getUser() === $wgUser->getName() ) {
914 - return $this->modifiedTime[$hash] = $wgUser->getTouched();
915 - } else {
916 - return 1;
917 - }
918 - }
919 -
920 - /**
921 - * Fetch the context's user options, or if it doesn't match current user,
922 - * the default options.
923 - *
924 - * @param $context ResourceLoaderContext
925 - * @return array
926 - */
927 - protected function contextUserOptions( ResourceLoaderContext $context ) {
928 - global $wgUser;
929 -
930 - // Verify identity -- this is a private module
931 - if ( $context->getUser() === $wgUser->getName() ) {
932 - return $wgUser->getOptions();
933 - } else {
934 - return User::getDefaultOptions();
935 - }
936 - }
937 -
938 - public function getScript( ResourceLoaderContext $context ) {
939 - $encOptions = FormatJson::encode( $this->contextUserOptions( $context ) );
940 - return "mediaWiki.user.options.set( $encOptions );";
941 - }
942 -
943 - public function getStyles( ResourceLoaderContext $context ) {
944 - global $wgAllowUserCssPrefs;
945 -
946 - if ( $wgAllowUserCssPrefs ) {
947 - $options = $this->contextUserOptions( $context );
948 -
949 - // Build CSS rules
950 - $rules = array();
951 - if ( $options['underline'] < 2 ) {
952 - $rules[] = "a { text-decoration: " . ( $options['underline'] ? 'underline' : 'none' ) . "; }";
953 - }
954 - if ( $options['highlightbroken'] ) {
955 - $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
956 - } else {
957 - $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
958 - $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
959 - $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
960 - }
961 - if ( $options['justify'] ) {
962 - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
963 - }
964 - if ( !$options['showtoc'] ) {
965 - $rules[] = "#toc { display: none; }\n";
966 - }
967 - if ( !$options['editsection'] ) {
968 - $rules[] = ".editsection { display: none; }\n";
969 - }
970 - if ( $options['editfont'] !== 'default' ) {
971 - $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
972 - }
973 - return array( 'all' => implode( "\n", $rules ) );
974 - }
975 - return array();
976 - }
977 -
978 - public function getFlip( $context ) {
979 - global $wgContLang;
980 -
981 - return $wgContLang->getDir() !== $context->getDirection();
982 - }
983 -
984 - public function getGroup() {
985 - return 'private';
986 - }
987 -}
988 -
98926 class ResourceLoaderStartUpModule extends ResourceLoaderModule {
99027 /* Protected Members */
99128
Index: trunk/phase3/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -23,723 +23,6 @@
2424 defined( 'MEDIAWIKI' ) || die( 1 );
2525
2626 /**
27 - * Abstraction for resource loader modules, with name registration and maxage functionality.
28 - */
29 -abstract class ResourceLoaderModule {
30 -
31 - /* Protected Members */
32 -
33 - protected $name = null;
34 -
35 - // In-object cache for file dependencies
36 - protected $fileDeps = array();
37 - // In-object cache for message blob mtime
38 - protected $msgBlobMtime = array();
39 -
40 - /* Methods */
41 -
42 - /**
43 - * Get this module's name. This is set when the module is registered
44 - * with ResourceLoader::register()
45 - *
46 - * @return Mixed: name (string) or null if no name was set
47 - */
48 - public function getName() {
49 - return $this->name;
50 - }
51 -
52 - /**
53 - * Set this module's name. This is called by ResourceLodaer::register()
54 - * when registering the module. Other code should not call this.
55 - *
56 - * @param $name String: name
57 - */
58 - public function setName( $name ) {
59 - $this->name = $name;
60 - }
61 -
62 - /**
63 - * Get whether CSS for this module should be flipped
64 - */
65 - public function getFlip( $context ) {
66 - return $context->getDirection() === 'rtl';
67 - }
68 -
69 - /**
70 - * Get all JS for this module for a given language and skin.
71 - * Includes all relevant JS except loader scripts.
72 - *
73 - * @param $context ResourceLoaderContext object
74 - * @return String: JS
75 - */
76 - public function getScript( ResourceLoaderContext $context ) {
77 - // Stub, override expected
78 - return '';
79 - }
80 -
81 - /**
82 - * Get all CSS for this module for a given skin.
83 - *
84 - * @param $context ResourceLoaderContext object
85 - * @return array: strings of CSS keyed by media type
86 - */
87 - public function getStyles( ResourceLoaderContext $context ) {
88 - // Stub, override expected
89 - return '';
90 - }
91 -
92 - /**
93 - * Get the messages needed for this module.
94 - *
95 - * To get a JSON blob with messages, use MessageBlobStore::get()
96 - *
97 - * @return array of message keys. Keys may occur more than once
98 - */
99 - public function getMessages() {
100 - // Stub, override expected
101 - return array();
102 - }
103 -
104 - /**
105 - * Get the group this module is in.
106 - *
107 - * @return string of group name
108 - */
109 - public function getGroup() {
110 - // Stub, override expected
111 - return null;
112 - }
113 -
114 - /**
115 - * Get the loader JS for this module, if set.
116 - *
117 - * @return Mixed: loader JS (string) or false if no custom loader set
118 - */
119 - public function getLoaderScript() {
120 - // Stub, override expected
121 - return false;
122 - }
123 -
124 - /**
125 - * Get a list of modules this module depends on.
126 - *
127 - * Dependency information is taken into account when loading a module
128 - * on the client side. When adding a module on the server side,
129 - * dependency information is NOT taken into account and YOU are
130 - * responsible for adding dependent modules as well. If you don't do
131 - * this, the client side loader will send a second request back to the
132 - * server to fetch the missing modules, which kind of defeats the
133 - * purpose of the resource loader.
134 - *
135 - * To add dependencies dynamically on the client side, use a custom
136 - * loader script, see getLoaderScript()
137 - * @return Array of module names (strings)
138 - */
139 - public function getDependencies() {
140 - // Stub, override expected
141 - return array();
142 - }
143 -
144 - /**
145 - * Get the files this module depends on indirectly for a given skin.
146 - * Currently these are only image files referenced by the module's CSS.
147 - *
148 - * @param $skin String: skin name
149 - * @return array of files
150 - */
151 - public function getFileDependencies( $skin ) {
152 - // Try in-object cache first
153 - if ( isset( $this->fileDeps[$skin] ) ) {
154 - return $this->fileDeps[$skin];
155 - }
156 -
157 - $dbr = wfGetDB( DB_SLAVE );
158 - $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
159 - 'md_module' => $this->getName(),
160 - 'md_skin' => $skin,
161 - ), __METHOD__
162 - );
163 - if ( !is_null( $deps ) ) {
164 - return $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true );
165 - }
166 - return $this->fileDeps[$skin] = array();
167 - }
168 -
169 - /**
170 - * Set preloaded file dependency information. Used so we can load this
171 - * information for all modules at once.
172 - * @param $skin string Skin name
173 - * @param $deps array Array of file names
174 - */
175 - public function setFileDependencies( $skin, $deps ) {
176 - $this->fileDeps[$skin] = $deps;
177 - }
178 -
179 - /**
180 - * Get the last modification timestamp of the message blob for this
181 - * module in a given language.
182 - * @param $lang string Language code
183 - * @return int UNIX timestamp, or 0 if no blob found
184 - */
185 - public function getMsgBlobMtime( $lang ) {
186 - if ( !count( $this->getMessages() ) )
187 - return 0;
188 -
189 - $dbr = wfGetDB( DB_SLAVE );
190 - $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
191 - 'mr_resource' => $this->getName(),
192 - 'mr_lang' => $lang
193 - ), __METHOD__
194 - );
195 - $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
196 - return $this->msgBlobMtime[$lang];
197 - }
198 -
199 - /**
200 - * Set a preloaded message blob last modification timestamp. Used so we
201 - * can load this information for all modules at once.
202 - * @param $lang string Language code
203 - * @param $mtime int UNIX timestamp or 0 if there is no such blob
204 - */
205 - public function setMsgBlobMtime( $lang, $mtime ) {
206 - $this->msgBlobMtime[$lang] = $mtime;
207 - }
208 -
209 - /* Abstract Methods */
210 -
211 - /**
212 - * Get this module's last modification timestamp for a given
213 - * combination of language, skin and debug mode flag. This is typically
214 - * the highest of each of the relevant components' modification
215 - * timestamps. Whenever anything happens that changes the module's
216 - * contents for these parameters, the mtime should increase.
217 - *
218 - * @param $context ResourceLoaderContext object
219 - * @return int UNIX timestamp
220 - */
221 - public function getModifiedTime( ResourceLoaderContext $context ) {
222 - // 0 would mean now
223 - return 1;
224 - }
225 -}
226 -
227 -/**
228 - * Module based on local JS/CSS files. This is the most common type of module.
229 - */
230 -class ResourceLoaderFileModule extends ResourceLoaderModule {
231 - /* Protected Members */
232 -
233 - protected $scripts = array();
234 - protected $styles = array();
235 - protected $messages = array();
236 - protected $group;
237 - protected $dependencies = array();
238 - protected $debugScripts = array();
239 - protected $languageScripts = array();
240 - protected $skinScripts = array();
241 - protected $skinStyles = array();
242 - protected $loaders = array();
243 - protected $parameters = array();
244 -
245 - // In-object cache for file dependencies
246 - protected $fileDeps = array();
247 - // In-object cache for mtime
248 - protected $modifiedTime = array();
249 -
250 - /* Methods */
251 -
252 - /**
253 - * Construct a new module from an options array.
254 - *
255 - * @param $options array Options array. If empty, an empty module will be constructed
256 - *
257 - * $options format:
258 - * array(
259 - * // Required module options (mutually exclusive)
260 - * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ),
261 - *
262 - * // Optional module options
263 - * 'languageScripts' => array(
264 - * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... )
265 - * ...
266 - * ),
267 - * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ),
268 - * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ),
269 - *
270 - * // Non-raw module options
271 - * 'dependencies' => 'module' | array( 'module1', 'module2' ... )
272 - * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ),
273 - * 'styles' => 'dir/file.css' | array( 'dir/file1.css', 'dir/file2.css' ... ), |
274 - * array( 'dir/file1.css' => array( 'media' => 'print' ) ),
275 - * 'skinStyles' => array(
276 - * '[skin name]' => 'dir/skin.css' | array( 'dir/skin1.css', 'dir/skin2.css' ... ) |
277 - * array( 'dir/file1.css' => array( 'media' => 'print' )
278 - * ...
279 - * ),
280 - * 'messages' => array( 'message1', 'message2' ... ),
281 - * 'group' => 'stuff',
282 - * )
283 - *
284 - * @param $basePath String: base path to prepend to all paths in $options
285 - */
286 - public function __construct( $options = array(), $basePath = null ) {
287 - foreach ( $options as $option => $value ) {
288 - switch ( $option ) {
289 - case 'scripts':
290 - case 'debugScripts':
291 - case 'languageScripts':
292 - case 'skinScripts':
293 - case 'loaders':
294 - $this->{$option} = (array)$value;
295 - // Automatically prefix script paths
296 - if ( is_string( $basePath ) ) {
297 - foreach ( $this->{$option} as $key => $value ) {
298 - $this->{$option}[$key] = $basePath . $value;
299 - }
300 - }
301 - break;
302 - case 'styles':
303 - case 'skinStyles':
304 - $this->{$option} = (array)$value;
305 - // Automatically prefix style paths
306 - if ( is_string( $basePath ) ) {
307 - foreach ( $this->{$option} as $key => $value ) {
308 - if ( is_array( $value ) ) {
309 - $this->{$option}[$basePath . $key] = $value;
310 - unset( $this->{$option}[$key] );
311 - } else {
312 - $this->{$option}[$key] = $basePath . $value;
313 - }
314 - }
315 - }
316 - break;
317 - case 'dependencies':
318 - case 'messages':
319 - $this->{$option} = (array)$value;
320 - break;
321 - case 'group':
322 - $this->group = (string)$value;
323 - break;
324 - }
325 - }
326 - }
327 -
328 - /**
329 - * Add script files to this module. In order to be valid, a module
330 - * must contain at least one script file.
331 - *
332 - * @param $scripts Mixed: path to script file (string) or array of paths
333 - */
334 - public function addScripts( $scripts ) {
335 - $this->scripts = array_merge( $this->scripts, (array)$scripts );
336 - }
337 -
338 - /**
339 - * Add style (CSS) files to this module.
340 - *
341 - * @param $styles Mixed: path to CSS file (string) or array of paths
342 - */
343 - public function addStyles( $styles ) {
344 - $this->styles = array_merge( $this->styles, (array)$styles );
345 - }
346 -
347 - /**
348 - * Add messages to this module.
349 - *
350 - * @param $messages Mixed: message key (string) or array of message keys
351 - */
352 - public function addMessages( $messages ) {
353 - $this->messages = array_merge( $this->messages, (array)$messages );
354 - }
355 -
356 - /**
357 - * Sets the group of this module.
358 - *
359 - * @param $group string group name
360 - */
361 - public function setGroup( $group ) {
362 - $this->group = $group;
363 - }
364 -
365 - /**
366 - * Add dependencies. Dependency information is taken into account when
367 - * loading a module on the client side. When adding a module on the
368 - * server side, dependency information is NOT taken into account and
369 - * YOU are responsible for adding dependent modules as well. If you
370 - * don't do this, the client side loader will send a second request
371 - * back to the server to fetch the missing modules, which kind of
372 - * defeats the point of using the resource loader in the first place.
373 - *
374 - * To add dependencies dynamically on the client side, use a custom
375 - * loader (see addLoaders())
376 - *
377 - * @param $dependencies Mixed: module name (string) or array of module names
378 - */
379 - public function addDependencies( $dependencies ) {
380 - $this->dependencies = array_merge( $this->dependencies, (array)$dependencies );
381 - }
382 -
383 - /**
384 - * Add debug scripts to the module. These scripts are only included
385 - * in debug mode.
386 - *
387 - * @param $scripts Mixed: path to script file (string) or array of paths
388 - */
389 - public function addDebugScripts( $scripts ) {
390 - $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts );
391 - }
392 -
393 - /**
394 - * Add language-specific scripts. These scripts are only included for
395 - * a given language.
396 - *
397 - * @param $lang String: language code
398 - * @param $scripts Mixed: path to script file (string) or array of paths
399 - */
400 - public function addLanguageScripts( $lang, $scripts ) {
401 - $this->languageScripts = array_merge_recursive(
402 - $this->languageScripts,
403 - array( $lang => $scripts )
404 - );
405 - }
406 -
407 - /**
408 - * Add skin-specific scripts. These scripts are only included for
409 - * a given skin.
410 - *
411 - * @param $skin String: skin name, or 'default'
412 - * @param $scripts Mixed: path to script file (string) or array of paths
413 - */
414 - public function addSkinScripts( $skin, $scripts ) {
415 - $this->skinScripts = array_merge_recursive(
416 - $this->skinScripts,
417 - array( $skin => $scripts )
418 - );
419 - }
420 -
421 - /**
422 - * Add skin-specific CSS. These CSS files are only included for a
423 - * given skin. If there are no skin-specific CSS files for a skin,
424 - * the files defined for 'default' will be used, if any.
425 - *
426 - * @param $skin String: skin name, or 'default'
427 - * @param $scripts Mixed: path to CSS file (string) or array of paths
428 - */
429 - public function addSkinStyles( $skin, $scripts ) {
430 - $this->skinStyles = array_merge_recursive(
431 - $this->skinStyles,
432 - array( $skin => $scripts )
433 - );
434 - }
435 -
436 - /**
437 - * Add loader scripts. These scripts are loaded on every page and are
438 - * responsible for registering this module using
439 - * mediaWiki.loader.register(). If there are no loader scripts defined,
440 - * the resource loader will register the module itself.
441 - *
442 - * Loader scripts are used to determine a module's dependencies
443 - * dynamically on the client side (e.g. based on browser type/version).
444 - * Note that loader scripts are included on every page, so they should
445 - * be lightweight and use mediaWiki.loader.register()'s callback
446 - * feature to defer dependency calculation.
447 - *
448 - * @param $scripts Mixed: path to script file (string) or array of paths
449 - */
450 - public function addLoaders( $scripts ) {
451 - $this->loaders = array_merge( $this->loaders, (array)$scripts );
452 - }
453 -
454 - public function getScript( ResourceLoaderContext $context ) {
455 - $retval = $this->getPrimaryScript() . "\n" .
456 - $this->getLanguageScript( $context->getLanguage() ) . "\n" .
457 - $this->getSkinScript( $context->getSkin() );
458 -
459 - if ( $context->getDebug() ) {
460 - $retval .= $this->getDebugScript();
461 - }
462 -
463 - return $retval;
464 - }
465 -
466 - public function getStyles( ResourceLoaderContext $context ) {
467 - $styles = array();
468 - foreach ( $this->getPrimaryStyles() as $media => $style ) {
469 - if ( !isset( $styles[$media] ) ) {
470 - $styles[$media] = '';
471 - }
472 - $styles[$media] .= $style;
473 - }
474 - foreach ( $this->getSkinStyles( $context->getSkin() ) as $media => $style ) {
475 - if ( !isset( $styles[$media] ) ) {
476 - $styles[$media] = '';
477 - }
478 - $styles[$media] .= $style;
479 - }
480 -
481 - // Collect referenced files
482 - $files = array();
483 - foreach ( $styles as $style ) {
484 - // Extract and store the list of referenced files
485 - $files = array_merge( $files, CSSMin::getLocalFileReferences( $style ) );
486 - }
487 -
488 - // Only store if modified
489 - if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) {
490 - $encFiles = FormatJson::encode( $files );
491 - $dbw = wfGetDB( DB_MASTER );
492 - $dbw->replace( 'module_deps',
493 - array( array( 'md_module', 'md_skin' ) ), array(
494 - 'md_module' => $this->getName(),
495 - 'md_skin' => $context->getSkin(),
496 - 'md_deps' => $encFiles,
497 - )
498 - );
499 - }
500 -
501 - return $styles;
502 - }
503 -
504 - public function getMessages() {
505 - return $this->messages;
506 - }
507 -
508 - public function getGroup() {
509 - return $this->group;
510 - }
511 -
512 - public function getDependencies() {
513 - return $this->dependencies;
514 - }
515 -
516 - public function getLoaderScript() {
517 - if ( count( $this->loaders ) == 0 ) {
518 - return false;
519 - }
520 -
521 - return self::concatScripts( $this->loaders );
522 - }
523 -
524 - /**
525 - * Get the last modified timestamp of this module, which is calculated
526 - * as the highest last modified timestamp of its constituent files and
527 - * the files it depends on (see getFileDependencies()). Only files
528 - * relevant to the given language and skin are taken into account, and
529 - * files only relevant in debug mode are not taken into account when
530 - * debug mode is off.
531 - *
532 - * @param $context ResourceLoaderContext object
533 - * @return Integer: UNIX timestamp
534 - */
535 - public function getModifiedTime( ResourceLoaderContext $context ) {
536 - if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
537 - return $this->modifiedTime[$context->getHash()];
538 - }
539 - wfProfileIn( __METHOD__ );
540 -
541 - // Sort of nasty way we can get a flat list of files depended on by all styles
542 - $styles = array();
543 - foreach ( self::organizeFilesByOption( $this->styles, 'media', 'all' ) as $styleFiles ) {
544 - $styles = array_merge( $styles, $styleFiles );
545 - }
546 - $skinFiles = (array) self::getSkinFiles(
547 - $context->getSkin(), self::organizeFilesByOption( $this->skinStyles, 'media', 'all' )
548 - );
549 - foreach ( $skinFiles as $styleFiles ) {
550 - $styles = array_merge( $styles, $styleFiles );
551 - }
552 -
553 - // Final merge, this should result in a master list of dependent files
554 - $files = array_merge(
555 - $this->scripts,
556 - $styles,
557 - $context->getDebug() ? $this->debugScripts : array(),
558 - isset( $this->languageScripts[$context->getLanguage()] ) ?
559 - (array) $this->languageScripts[$context->getLanguage()] : array(),
560 - (array) self::getSkinFiles( $context->getSkin(), $this->skinScripts ),
561 - $this->loaders,
562 - $this->getFileDependencies( $context->getSkin() )
563 - );
564 -
565 - wfProfileIn( __METHOD__.'-filemtime' );
566 - $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) );
567 - wfProfileOut( __METHOD__.'-filemtime' );
568 - $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) );
569 - wfProfileOut( __METHOD__ );
570 - return $this->modifiedTime[$context->getHash()];
571 - }
572 -
573 - /* Protected Members */
574 -
575 - /**
576 - * Get the primary JS for this module. This is pulled from the
577 - * script files added through addScripts()
578 - *
579 - * @return String: JS
580 - */
581 - protected function getPrimaryScript() {
582 - return self::concatScripts( $this->scripts );
583 - }
584 -
585 - /**
586 - * Get the primary CSS for this module. This is pulled from the CSS
587 - * files added through addStyles()
588 - *
589 - * @return Array
590 - */
591 - protected function getPrimaryStyles() {
592 - return self::concatStyles( $this->styles );
593 - }
594 -
595 - /**
596 - * Get the debug JS for this module. This is pulled from the script
597 - * files added through addDebugScripts()
598 - *
599 - * @return String: JS
600 - */
601 - protected function getDebugScript() {
602 - return self::concatScripts( $this->debugScripts );
603 - }
604 -
605 - /**
606 - * Get the language-specific JS for a given language. This is pulled
607 - * from the language-specific script files added through addLanguageScripts()
608 - *
609 - * @return String: JS
610 - */
611 - protected function getLanguageScript( $lang ) {
612 - if ( !isset( $this->languageScripts[$lang] ) ) {
613 - return '';
614 - }
615 - return self::concatScripts( $this->languageScripts[$lang] );
616 - }
617 -
618 - /**
619 - * Get the skin-specific JS for a given skin. This is pulled from the
620 - * skin-specific JS files added through addSkinScripts()
621 - *
622 - * @return String: JS
623 - */
624 - protected function getSkinScript( $skin ) {
625 - return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) );
626 - }
627 -
628 - /**
629 - * Get the skin-specific CSS for a given skin. This is pulled from the
630 - * skin-specific CSS files added through addSkinStyles()
631 - *
632 - * @return Array: list of CSS strings keyed by media type
633 - */
634 - protected function getSkinStyles( $skin ) {
635 - return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) );
636 - }
637 -
638 - /**
639 - * Helper function to get skin-specific data from an array.
640 - *
641 - * @param $skin String: skin name
642 - * @param $map Array: map of skin names to arrays
643 - * @return $map[$skin] if set and non-empty, or $map['default'] if set, or an empty array
644 - */
645 - protected static function getSkinFiles( $skin, $map ) {
646 - $retval = array();
647 -
648 - if ( isset( $map[$skin] ) && $map[$skin] ) {
649 - $retval = $map[$skin];
650 - } else if ( isset( $map['default'] ) ) {
651 - $retval = $map['default'];
652 - }
653 -
654 - return $retval;
655 - }
656 -
657 - /**
658 - * Get the contents of a set of files and concatenate them, with
659 - * newlines in between. Each file is used only once.
660 - *
661 - * @param $files Array of file names
662 - * @return String: concatenated contents of $files
663 - */
664 - protected static function concatScripts( $files ) {
665 - return implode( "\n",
666 - array_map(
667 - 'file_get_contents',
668 - array_map(
669 - array( __CLASS__, 'remapFilename' ),
670 - array_unique( (array) $files ) ) ) );
671 - }
672 -
673 - protected static function organizeFilesByOption( $files, $option, $default ) {
674 - $organizedFiles = array();
675 - foreach ( (array) $files as $key => $value ) {
676 - if ( is_int( $key ) ) {
677 - // File name as the value
678 - if ( !isset( $organizedFiles[$default] ) ) {
679 - $organizedFiles[$default] = array();
680 - }
681 - $organizedFiles[$default][] = $value;
682 - } else if ( is_array( $value ) ) {
683 - // File name as the key, options array as the value
684 - $media = isset( $value[$option] ) ? $value[$option] : $default;
685 - if ( !isset( $organizedFiles[$media] ) ) {
686 - $organizedFiles[$media] = array();
687 - }
688 - $organizedFiles[$media][] = $key;
689 - }
690 - }
691 - return $organizedFiles;
692 - }
693 -
694 - /**
695 - * Get the contents of a set of CSS files, remap then and concatenate
696 - * them, with newlines in between. Each file is used only once.
697 - *
698 - * @param $styles Array of file names
699 - * @return Array: list of concatenated and remapped contents of $files keyed by media type
700 - */
701 - protected static function concatStyles( $styles ) {
702 - $styles = self::organizeFilesByOption( $styles, 'media', 'all' );
703 - foreach ( $styles as $media => $files ) {
704 - $styles[$media] =
705 - implode( "\n",
706 - array_map(
707 - array( __CLASS__, 'remapStyle' ),
708 - array_unique( (array) $files ) ) );
709 - }
710 - return $styles;
711 - }
712 -
713 - /**
714 - * Remap a relative to $IP. Used as a callback for array_map()
715 - *
716 - * @param $file String: file name
717 - * @return string $IP/$file
718 - */
719 - protected static function remapFilename( $file ) {
720 - global $IP;
721 -
722 - return "$IP/$file";
723 - }
724 -
725 - /**
726 - * Get the contents of a CSS file and run it through CSSMin::remap().
727 - * This wrapper is needed so we can use array_map() in concatStyles()
728 - *
729 - * @param $file String: file name
730 - * @return string Remapped CSS
731 - */
732 - protected static function remapStyle( $file ) {
733 - global $wgScriptPath;
734 - return CSSMin::remap(
735 - file_get_contents( self::remapFilename( $file ) ),
736 - dirname( $file ),
737 - $wgScriptPath . '/' . dirname( $file ),
738 - true
739 - );
740 - }
741 -}
742 -
743 -/**
74427 * Abstraction for resource loader modules which pull from wiki pages
74528 *
74629 * This can only be used for wiki pages in the MediaWiki and User namespaces, because of it's dependence on the
@@ -830,339 +113,3 @@
831114 return $this->modifiedTime[$hash] = $modifiedTime;
832115 }
833116 }
834 -
835 -/**
836 - * Module for site customizations
837 - */
838 -class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
839 -
840 - /* Protected Methods */
841 -
842 - protected function getPages( ResourceLoaderContext $context ) {
843 - global $wgHandheldStyle;
844 -
845 - $pages = array(
846 - 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
847 - 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
848 - ucfirst( $context->getSkin() ) . '.js' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'script' ),
849 - ucfirst( $context->getSkin() ) . '.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style' ),
850 - 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'print' ),
851 - );
852 - if ( $wgHandheldStyle ) {
853 - $pages['Handheld.css'] = array( 'ns' => NS_MEDIAWIKI, 'type' => 'style', 'media' => 'handheld' );
854 - }
855 - return $pages;
856 - }
857 -
858 - /* Methods */
859 -
860 - public function getGroup() {
861 - return 'site';
862 - }
863 -}
864 -
865 -/**
866 - * Module for user customizations
867 - */
868 -class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
869 -
870 - /* Protected Methods */
871 -
872 - protected function getPages( ResourceLoaderContext $context ) {
873 - global $wgAllowUserCss;
874 -
875 - if ( $context->getUser() && $wgAllowUserCss ) {
876 - $username = $context->getUser();
877 - return array(
878 - "$username/common.js" => array( 'ns' => NS_USER, 'type' => 'script' ),
879 - "$username/" . $context->getSkin() . '.js' => array( 'ns' => NS_USER, 'type' => 'script' ),
880 - "$username/common.css" => array( 'ns' => NS_USER, 'type' => 'style' ),
881 - "$username/" . $context->getSkin() . '.css' => array( 'ns' => NS_USER, 'type' => 'style' ),
882 - );
883 - }
884 - return array();
885 - }
886 -
887 - /* Methods */
888 -
889 - public function getGroup() {
890 - return 'user';
891 - }
892 -}
893 -
894 -/**
895 - * Module for user preference customizations
896 - */
897 -class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
898 -
899 - /* Protected Members */
900 -
901 - protected $modifiedTime = array();
902 -
903 - /* Methods */
904 -
905 - public function getModifiedTime( ResourceLoaderContext $context ) {
906 - $hash = $context->getHash();
907 - if ( isset( $this->modifiedTime[$hash] ) ) {
908 - return $this->modifiedTime[$hash];
909 - }
910 -
911 - global $wgUser;
912 -
913 - if ( $context->getUser() === $wgUser->getName() ) {
914 - return $this->modifiedTime[$hash] = $wgUser->getTouched();
915 - } else {
916 - return 1;
917 - }
918 - }
919 -
920 - /**
921 - * Fetch the context's user options, or if it doesn't match current user,
922 - * the default options.
923 - *
924 - * @param $context ResourceLoaderContext
925 - * @return array
926 - */
927 - protected function contextUserOptions( ResourceLoaderContext $context ) {
928 - global $wgUser;
929 -
930 - // Verify identity -- this is a private module
931 - if ( $context->getUser() === $wgUser->getName() ) {
932 - return $wgUser->getOptions();
933 - } else {
934 - return User::getDefaultOptions();
935 - }
936 - }
937 -
938 - public function getScript( ResourceLoaderContext $context ) {
939 - $encOptions = FormatJson::encode( $this->contextUserOptions( $context ) );
940 - return "mediaWiki.user.options.set( $encOptions );";
941 - }
942 -
943 - public function getStyles( ResourceLoaderContext $context ) {
944 - global $wgAllowUserCssPrefs;
945 -
946 - if ( $wgAllowUserCssPrefs ) {
947 - $options = $this->contextUserOptions( $context );
948 -
949 - // Build CSS rules
950 - $rules = array();
951 - if ( $options['underline'] < 2 ) {
952 - $rules[] = "a { text-decoration: " . ( $options['underline'] ? 'underline' : 'none' ) . "; }";
953 - }
954 - if ( $options['highlightbroken'] ) {
955 - $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
956 - } else {
957 - $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
958 - $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
959 - $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
960 - }
961 - if ( $options['justify'] ) {
962 - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
963 - }
964 - if ( !$options['showtoc'] ) {
965 - $rules[] = "#toc { display: none; }\n";
966 - }
967 - if ( !$options['editsection'] ) {
968 - $rules[] = ".editsection { display: none; }\n";
969 - }
970 - if ( $options['editfont'] !== 'default' ) {
971 - $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
972 - }
973 - return array( 'all' => implode( "\n", $rules ) );
974 - }
975 - return array();
976 - }
977 -
978 - public function getFlip( $context ) {
979 - global $wgContLang;
980 -
981 - return $wgContLang->getDir() !== $context->getDirection();
982 - }
983 -
984 - public function getGroup() {
985 - return 'private';
986 - }
987 -}
988 -
989 -class ResourceLoaderStartUpModule extends ResourceLoaderModule {
990 - /* Protected Members */
991 -
992 - protected $modifiedTime = array();
993 -
994 - /* Protected Methods */
995 -
996 - protected function getConfig( $context ) {
997 - global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
998 - $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgBreakFrames,
999 - $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
1000 - $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
1001 - $wgSitename, $wgFileExtensions;
1002 -
1003 - // Pre-process information
1004 - $separatorTransTable = $wgContLang->separatorTransformTable();
1005 - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
1006 - $compactSeparatorTransTable = array(
1007 - implode( "\t", array_keys( $separatorTransTable ) ),
1008 - implode( "\t", $separatorTransTable ),
1009 - );
1010 - $digitTransTable = $wgContLang->digitTransformTable();
1011 - $digitTransTable = $digitTransTable ? $digitTransTable : array();
1012 - $compactDigitTransTable = array(
1013 - implode( "\t", array_keys( $digitTransTable ) ),
1014 - implode( "\t", $digitTransTable ),
1015 - );
1016 - $mainPage = Title::newMainPage();
1017 -
1018 - // Build list of variables
1019 - $vars = array(
1020 - 'wgLoadScript' => $wgLoadScript,
1021 - 'debug' => $context->getDebug(),
1022 - 'skin' => $context->getSkin(),
1023 - 'stylepath' => $wgStylePath,
1024 - 'wgUrlProtocols' => wfUrlProtocols(),
1025 - 'wgArticlePath' => $wgArticlePath,
1026 - 'wgScriptPath' => $wgScriptPath,
1027 - 'wgScriptExtension' => $wgScriptExtension,
1028 - 'wgScript' => $wgScript,
1029 - 'wgVariantArticlePath' => $wgVariantArticlePath,
1030 - 'wgActionPaths' => $wgActionPaths,
1031 - 'wgServer' => $wgServer,
1032 - 'wgUserLanguage' => $context->getLanguage(),
1033 - 'wgContentLanguage' => $wgContLang->getCode(),
1034 - 'wgBreakFrames' => $wgBreakFrames,
1035 - 'wgVersion' => $wgVersion,
1036 - 'wgEnableAPI' => $wgEnableAPI,
1037 - 'wgEnableWriteAPI' => $wgEnableWriteAPI,
1038 - 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
1039 - 'wgDigitTransformTable' => $compactDigitTransTable,
1040 - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
1041 - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
1042 - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
1043 - 'wgSiteName' => $wgSitename,
1044 - 'wgFileExtensions' => $wgFileExtensions,
1045 - 'wgDBname' => $wgDBname,
1046 - );
1047 - if ( $wgContLang->hasVariants() ) {
1048 - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
1049 - }
1050 - if ( $wgUseAjax && $wgEnableMWSuggest ) {
1051 - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
1052 - }
1053 -
1054 - return $vars;
1055 - }
1056 -
1057 - /**
1058 - * Gets registration code for all modules
1059 - *
1060 - * @param $context ResourceLoaderContext object
1061 - * @return String: JavaScript code for registering all modules with the client loader
1062 - */
1063 - public static function getModuleRegistrations( ResourceLoaderContext $context ) {
1064 - global $wgCacheEpoch;
1065 - wfProfileIn( __METHOD__ );
1066 -
1067 - $out = '';
1068 - $registrations = array();
1069 - foreach ( $context->getResourceLoader()->getModules() as $name => $module ) {
1070 - // Support module loader scripts
1071 - if ( ( $loader = $module->getLoaderScript() ) !== false ) {
1072 - $deps = $module->getDependencies();
1073 - $group = $module->getGroup();
1074 - $version = wfTimestamp( TS_ISO_8601_BASIC, round( $module->getModifiedTime( $context ), -2 ) );
1075 - $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
1076 - }
1077 - // Automatically register module
1078 - else {
1079 - $mtime = max( $module->getModifiedTime( $context ), wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
1080 - // Modules without dependencies or a group pass two arguments (name, timestamp) to
1081 - // mediaWiki.loader.register()
1082 - if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
1083 - $registrations[] = array( $name, $mtime );
1084 - }
1085 - // Modules with dependencies but no group pass three arguments (name, timestamp, dependencies)
1086 - // to mediaWiki.loader.register()
1087 - else if ( $module->getGroup() === null ) {
1088 - $registrations[] = array(
1089 - $name, $mtime, $module->getDependencies() );
1090 - }
1091 - // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
1092 - // to mediaWiki.loader.register()
1093 - else {
1094 - $registrations[] = array(
1095 - $name, $mtime, $module->getDependencies(), $module->getGroup() );
1096 - }
1097 - }
1098 - }
1099 - $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
1100 -
1101 - wfProfileOut( __METHOD__ );
1102 - return $out;
1103 - }
1104 -
1105 - /* Methods */
1106 -
1107 - public function getScript( ResourceLoaderContext $context ) {
1108 - global $IP, $wgLoadScript;
1109 -
1110 - $out = file_get_contents( "$IP/resources/startup.js" );
1111 - if ( $context->getOnly() === 'scripts' ) {
1112 - // Build load query for jquery and mediawiki modules
1113 - $query = array(
1114 - 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
1115 - 'only' => 'scripts',
1116 - 'lang' => $context->getLanguage(),
1117 - 'skin' => $context->getSkin(),
1118 - 'debug' => $context->getDebug() ? 'true' : 'false',
1119 - 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
1120 - $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
1121 - $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
1122 - ), -2 ) )
1123 - );
1124 - // Ensure uniform query order
1125 - ksort( $query );
1126 -
1127 - // Startup function
1128 - $configuration = FormatJson::encode( $this->getConfig( $context ) );
1129 - $registrations = self::getModuleRegistrations( $context );
1130 - $out .= "var startUp = function() {\n\t$registrations\n\tmediaWiki.config.set( $configuration );\n};";
1131 -
1132 - // Conditional script injection
1133 - $scriptTag = Xml::escapeJsString( Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) );
1134 - $out .= "if ( isCompatible() ) {\n\tdocument.write( '$scriptTag' );\n}\ndelete isCompatible;";
1135 - }
1136 -
1137 - return $out;
1138 - }
1139 -
1140 - public function getModifiedTime( ResourceLoaderContext $context ) {
1141 - global $IP, $wgCacheEpoch;
1142 -
1143 - $hash = $context->getHash();
1144 - if ( isset( $this->modifiedTime[$hash] ) ) {
1145 - return $this->modifiedTime[$hash];
1146 - }
1147 - $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
1148 -
1149 - // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully
1150 - // before making changes to this code!
1151 - $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
1152 - foreach ( $context->getResourceLoader()->getModules() as $module ) {
1153 - $time = max( $time, $module->getModifiedTime( $context ) );
1154 - }
1155 - return $this->modifiedTime[$hash] = $time;
1156 - }
1157 -
1158 - public function getFlip( $context ) {
1159 - global $wgContLang;
1160 -
1161 - return $wgContLang->getDir() !== $context->getDirection();
1162 - }
1163 -
1164 - /* Methods */
1165 -
1166 - public function getGroup() {
1167 - return 'startup';
1168 - }
1169 -}
Index: trunk/phase3/includes/AutoLoader.php
@@ -200,12 +200,12 @@
201201 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
202202 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
203203 'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
204 - 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderModule.php',
205 - 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderModule.php',
206 - 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderModule.php',
207 - 'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderModule.php',
208 - 'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderModule.php',
209 - 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderModule.php',
 204+ 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
 205+ 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
 206+ 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
 207+ 'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
 208+ 'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
 209+ 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
210210 'ReverseChronologicalPager' => 'includes/Pager.php',
211211 'Revision' => 'includes/Revision.php',
212212 'RevisionDelete' => 'includes/RevisionDelete.php',

Comments

#Comment by Hashar (talk | contribs)   20:31, 19 October 2010

this is an elegant way to preserve commit history :)

Status & tagging log