Index: trunk/phase3/includes/ResourceLoaderContext.php |
— | — | @@ -31,23 +31,24 @@ |
32 | 32 | protected $language; |
33 | 33 | protected $direction; |
34 | 34 | protected $skin; |
| 35 | + protected $user; |
35 | 36 | protected $debug; |
36 | 37 | protected $only; |
37 | 38 | protected $hash; |
38 | 39 | |
39 | 40 | /* Methods */ |
40 | 41 | |
41 | | - public function __construct( WebRequest $request, $server ) { |
| 42 | + public function __construct( WebRequest $request ) { |
42 | 43 | global $wgLang, $wgDefaultSkin; |
43 | 44 | |
44 | 45 | $this->request = $request; |
45 | | - $this->server = $server; |
46 | 46 | // Interperet request |
47 | 47 | $this->modules = explode( '|', $request->getVal( 'modules' ) ); |
48 | 48 | $this->language = $request->getVal( 'lang' ); |
49 | 49 | $this->direction = $request->getVal( 'dir' ); |
50 | 50 | $this->skin = $request->getVal( 'skin' ); |
51 | | - $this->debug = $request->getVal( 'debug' ) === 'true' || $request->getBool( 'debug' ); |
| 51 | + $this->user = $request->getVal( 'user' ); |
| 52 | + $this->debug = $request->getBool( 'debug' ) && $request->getVal( 'debug' ) === 'true'; |
52 | 53 | $this->only = $request->getVal( 'only' ); |
53 | 54 | |
54 | 55 | // Fallback on system defaults |
— | — | @@ -68,10 +69,6 @@ |
69 | 70 | return $this->request; |
70 | 71 | } |
71 | 72 | |
72 | | - public function getServer() { |
73 | | - return $this->server; |
74 | | - } |
75 | | - |
76 | 73 | public function getModules() { |
77 | 74 | return $this->modules; |
78 | 75 | } |
— | — | @@ -88,6 +85,10 @@ |
89 | 86 | return $this->skin; |
90 | 87 | } |
91 | 88 | |
| 89 | + public function getUser() { |
| 90 | + return $this->skin; |
| 91 | + } |
| 92 | + |
92 | 93 | public function getDebug() { |
93 | 94 | return $this->debug; |
94 | 95 | } |
— | — | @@ -111,6 +112,6 @@ |
112 | 113 | public function getHash() { |
113 | 114 | return isset( $this->hash ) ? |
114 | 115 | $this->hash : $this->hash = |
115 | | - implode( '|', array( $this->language, $this->skin, $this->debug, $this->only ) ); |
| 116 | + implode( '|', array( $this->language, $this->skin, $this->user, $this->debug, $this->only ) ); |
116 | 117 | } |
117 | 118 | } |
Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -2283,19 +2283,33 @@ |
2284 | 2284 | static function makeResourceLoaderLink( $skin, $modules, $only ) { |
2285 | 2285 | global $wgUser, $wgLang, $wgRequest, $wgLoadScript; |
2286 | 2286 | // TODO: Should this be a static function of ResourceLoader instead? |
| 2287 | + // TODO: Divide off modules starting with "user", and add the user parameter to them |
2287 | 2288 | $query = array( |
2288 | | - 'modules' => implode( '|', array_unique( (array) $modules ) ), |
2289 | 2289 | 'lang' => $wgLang->getCode(), |
2290 | 2290 | 'debug' => $wgRequest->getBool( 'debug' ) && $wgRequest->getVal( 'debug' ) !== 'false', |
2291 | 2291 | 'skin' => $wgUser->getSkin()->getSkinName(), |
2292 | 2292 | 'only' => $only, |
2293 | 2293 | ); |
2294 | | - // Automatically select style/script elements |
2295 | | - if ( $only === 'styles' ) { |
2296 | | - return Html::linkedStyle( wfAppendQuery( $wgLoadScript, $query ) ); |
2297 | | - } else { |
2298 | | - return Html::linkedScript( wfAppendQuery( $wgLoadScript, $query ) ); |
| 2294 | + $moduleGroups = array( null => array(), 'user' => array() ); |
| 2295 | + foreach ( (array) $modules as $module ) { |
| 2296 | + $moduleGroups[strpos( $module, 'user' ) === 0 ? 'user' : null][] = $module; |
2299 | 2297 | } |
| 2298 | + $links = ''; |
| 2299 | + foreach ( $moduleGroups as $group => $modules ) { |
| 2300 | + if ( count( $modules ) ) { |
| 2301 | + $query['modules'] = implode( '|', array_unique( (array) $modules ) ); |
| 2302 | + if ( $group === 'user' ) { |
| 2303 | + $query['user'] = $wgUser->getName(); |
| 2304 | + } |
| 2305 | + // Automatically select style/script elements |
| 2306 | + if ( $only === 'styles' ) { |
| 2307 | + $links .= Html::linkedStyle( wfAppendQuery( $wgLoadScript, $query ) ); |
| 2308 | + } else { |
| 2309 | + $links .= Html::linkedScript( wfAppendQuery( $wgLoadScript, $query ) ); |
| 2310 | + } |
| 2311 | + } |
| 2312 | + } |
| 2313 | + return $links; |
2300 | 2314 | } |
2301 | 2315 | |
2302 | 2316 | /** |
— | — | @@ -2313,8 +2327,8 @@ |
2314 | 2328 | // Statup - this will immediately load jquery and mediawiki modules |
2315 | 2329 | $scripts = self::makeResourceLoaderLink( $sk, 'startup', 'scripts' ); |
2316 | 2330 | |
2317 | | - // Configuration -- this could be merged together with the load and go, but makeGlobalVariablesScript returns a |
2318 | | - // whole script tag -- grumble grumble |
| 2331 | + // Configuration -- This could be merged together with the load and go, but makeGlobalVariablesScript returns a |
| 2332 | + // whole script tag -- grumble grumble... |
2319 | 2333 | $scripts .= Skin::makeGlobalVariablesScript( $sk->getSkinName() ) . "\n"; |
2320 | 2334 | |
2321 | 2335 | // Script and Messages "only" |
Index: trunk/phase3/includes/Skin.php |
— | — | @@ -357,10 +357,10 @@ |
358 | 358 | |
359 | 359 | static function makeVariablesScript( $data ) { |
360 | 360 | if ( $data ) { |
361 | | - return Html::inlineScript( 'mediaWiki.config.set(' . json_encode( $data ) . ');' ); |
| 361 | + return Html::inlineScript( 'mediaWiki.config.set(' . FormatJson::encode( $data ) . ');' ); |
362 | 362 | } else { |
363 | 363 | return ''; |
364 | | - } |
| 364 | + } |
365 | 365 | } |
366 | 366 | |
367 | 367 | /** |
— | — | @@ -368,50 +368,16 @@ |
369 | 369 | * @param $skinName string Name of the skin |
370 | 370 | * The odd calling convention is for backwards compatibility |
371 | 371 | * @todo FIXME: Make this not depend on $wgTitle! |
| 372 | + * |
| 373 | + * Do not add things here which can be evaluated in ResourceLoaderStartupScript - in other words, without state. |
| 374 | + * You will only be adding bloat to the page and causing page caches to have to be purged on configuration changes. |
372 | 375 | */ |
373 | 376 | static function makeGlobalVariablesScript( $skinName ) { |
374 | | - if ( is_array( $skinName ) ) { |
375 | | - # Weird back-compat stuff. |
376 | | - $skinName = $skinName['skinname']; |
377 | | - } |
378 | | - |
379 | | - global $wgScript, $wgTitle, $wgStylePath, $wgUser, $wgScriptExtension; |
380 | | - global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang; |
381 | | - global $wgOut, $wgArticle; |
382 | | - global $wgBreakFrames, $wgRequest, $wgVariantArticlePath, $wgActionPaths; |
383 | | - global $wgUseAjax, $wgAjaxWatch; |
384 | | - global $wgVersion, $wgEnableAPI, $wgEnableWriteAPI; |
385 | | - global $wgRestrictionTypes; |
386 | | - global $wgDBname, $wgEnableMWSuggest; |
387 | | - global $wgSitename; |
388 | | - |
| 377 | + global $wgTitle, $wgUser, $wgRequest, $wgArticle, $wgOut, $wgRestrictionTypes; |
| 378 | + |
389 | 379 | $ns = $wgTitle->getNamespace(); |
390 | 380 | $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $wgTitle->getNsText(); |
391 | | - $separatorTransTable = $wgContLang->separatorTransformTable(); |
392 | | - $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); |
393 | | - $compactSeparatorTransTable = array( |
394 | | - implode( "\t", array_keys( $separatorTransTable ) ), |
395 | | - implode( "\t", $separatorTransTable ), |
396 | | - ); |
397 | | - $digitTransTable = $wgContLang->digitTransformTable(); |
398 | | - $digitTransTable = $digitTransTable ? $digitTransTable : array(); |
399 | | - $compactDigitTransTable = array( |
400 | | - implode( "\t", array_keys( $digitTransTable ) ), |
401 | | - implode( "\t", $digitTransTable ), |
402 | | - ); |
403 | | - |
404 | | - $mainPage = Title::newMainPage(); |
405 | 381 | $vars = array( |
406 | | - 'skin' => $skinName, |
407 | | - 'stylepath' => $wgStylePath, |
408 | | - 'wgUrlProtocols' => wfUrlProtocols(), |
409 | | - 'wgArticlePath' => $wgArticlePath, |
410 | | - 'wgScriptPath' => $wgScriptPath, |
411 | | - 'wgScriptExtension' => $wgScriptExtension, |
412 | | - 'wgScript' => $wgScript, |
413 | | - 'wgVariantArticlePath' => $wgVariantArticlePath, |
414 | | - 'wgActionPaths' => (object)$wgActionPaths, |
415 | | - 'wgServer' => $wgServer, |
416 | 382 | 'wgCanonicalNamespace' => $nsname, |
417 | 383 | 'wgCanonicalSpecialPageName' => $ns == NS_SPECIAL ? |
418 | 384 | SpecialPage::resolveAlias( $wgTitle->getDBkey() ) : false, # bug 21115 |
— | — | @@ -423,56 +389,16 @@ |
424 | 390 | 'wgIsArticle' => $wgOut->isArticle(), |
425 | 391 | 'wgUserName' => $wgUser->isAnon() ? null : $wgUser->getName(), |
426 | 392 | 'wgUserGroups' => $wgUser->getEffectiveGroups(), |
427 | | - 'wgUserLanguage' => $wgLang->getCode(), |
428 | | - 'wgContentLanguage' => $wgContLang->getCode(), |
429 | | - 'wgBreakFrames' => $wgBreakFrames, |
430 | 393 | 'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0, |
431 | | - 'wgVersion' => $wgVersion, |
432 | | - 'wgEnableAPI' => $wgEnableAPI, |
433 | | - 'wgEnableWriteAPI' => $wgEnableWriteAPI, |
434 | | - 'wgSeparatorTransformTable' => $compactSeparatorTransTable, |
435 | | - 'wgDigitTransformTable' => $compactDigitTransTable, |
436 | | - 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null, |
437 | | - 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(), |
438 | | - 'wgNamespaceIds' => $wgContLang->getNamespaceIds(), |
439 | | - 'wgSiteName' => $wgSitename, |
440 | 394 | 'wgCategories' => $wgOut->getCategories(), |
441 | 395 | ); |
442 | | - |
443 | | - if ( $wgContLang->hasVariants() ) { |
444 | | - $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); |
445 | | - } |
446 | | - |
447 | | - // if on upload page output the extension list & js_upload |
448 | | - if ( SpecialPage::resolveAlias( $wgTitle->getDBkey() ) == 'Upload' ) { |
449 | | - global $wgFileExtensions; |
450 | | - $vars['wgFileExtensions'] = $wgFileExtensions; |
451 | | - } |
452 | | - |
453 | | - if ( $wgUseAjax && $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ) { |
454 | | - $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate(); |
455 | | - $vars['wgDBname'] = $wgDBname; |
456 | | - $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $wgUser ); |
457 | | - $vars['wgMWSuggestMessages'] = array( wfMsg( 'search-mwsuggest-enabled' ), wfMsg( 'search-mwsuggest-disabled' ) ); |
458 | | - } |
459 | | - |
460 | 396 | foreach ( $wgRestrictionTypes as $type ) { |
461 | 397 | $vars['wgRestriction' . ucfirst( $type )] = $wgTitle->getRestrictions( $type ); |
462 | 398 | } |
463 | | - |
464 | | - if ( $wgOut->isArticleRelated() && $wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) { |
465 | | - $msgs = (object)array(); |
466 | | - |
467 | | - foreach ( array( 'watch', 'unwatch', 'watching', 'unwatching', |
468 | | - 'tooltip-ca-watch', 'tooltip-ca-unwatch' ) as $msgName ) { |
469 | | - $msgs-> { $msgName . 'Msg' } = wfMsg( $msgName ); |
470 | | - } |
471 | | - $vars['wgAjaxWatch'] = $msgs; |
472 | | - } |
473 | | - |
| 399 | + |
474 | 400 | // Allow extensions to add their custom variables to the global JS variables |
475 | 401 | wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars ) ); |
476 | | - |
| 402 | + |
477 | 403 | return self::makeVariablesScript( $vars ); |
478 | 404 | } |
479 | 405 | |
— | — | @@ -521,51 +447,20 @@ |
522 | 448 | * @return string |
523 | 449 | */ |
524 | 450 | public function generateUserJs( $skinName = null ) { |
525 | | - global $wgStylePath; |
526 | | - |
527 | | - wfProfileIn( __METHOD__ ); |
528 | | - |
529 | | - if ( !$skinName ) { |
530 | | - $skinName = $this->getSkinName(); |
531 | | - } |
532 | | - |
533 | | - $s = "/* generated javascript */\n"; |
534 | | - $s .= "var skin = '" . Xml::escapeJsString( $skinName ) . "';\n"; |
535 | | - $s .= "var stylepath = '" . Xml::escapeJsString( $wgStylePath ) . "';"; |
536 | | - $s .= "\n\n/* MediaWiki:Common.js */\n"; |
537 | | - |
538 | | - $commonJs = wfMsgExt( 'common.js', 'content' ); |
539 | | - |
540 | | - if ( !wfEmptyMsg( 'common.js', $commonJs ) ) { |
541 | | - $s .= $commonJs; |
542 | | - } |
543 | | - |
544 | | - $s .= "\n\n/* MediaWiki:" . ucfirst( $skinName ) . ".js */\n"; |
545 | | - |
546 | | - // avoid inclusion of non defined user JavaScript (with custom skins only) |
547 | | - // by checking for default message content |
548 | | - $msgKey = ucfirst( $skinName ) . '.js'; |
549 | | - $userJS = wfMsgExt( $msgKey, 'content' ); |
550 | | - |
551 | | - if ( !wfEmptyMsg( $msgKey, $userJS ) ) { |
552 | | - $s .= $userJS; |
553 | | - } |
554 | | - |
555 | | - wfProfileOut( __METHOD__ ); |
556 | | - return $s; |
| 451 | + |
| 452 | + // Stub - see ResourceLoaderSiteModule, CologneBlue, Simple and Standard skins override this |
| 453 | + |
| 454 | + return ''; |
557 | 455 | } |
558 | 456 | |
559 | 457 | /** |
560 | 458 | * Generate user stylesheet for action=raw&gen=css |
561 | 459 | */ |
562 | 460 | public function generateUserStylesheet() { |
563 | | - wfProfileIn( __METHOD__ ); |
564 | | - |
565 | | - $s = "/* generated user stylesheet */\n" . |
566 | | - $this->reallyGenerateUserStylesheet(); |
567 | | - |
568 | | - wfProfileOut( __METHOD__ ); |
569 | | - return $s; |
| 461 | + |
| 462 | + // Stub - see ResourceLoaderUserModule, CologneBlue, Simple and Standard skins override this |
| 463 | + |
| 464 | + return ''; |
570 | 465 | } |
571 | 466 | |
572 | 467 | /** |
— | — | @@ -573,53 +468,10 @@ |
574 | 469 | * Anything in here won't be generated if $wgAllowUserCssPrefs is false. |
575 | 470 | */ |
576 | 471 | protected function reallyGenerateUserStylesheet() { |
577 | | - global $wgUser; |
578 | | - |
579 | | - $s = ''; |
580 | | - |
581 | | - if ( ( $undopt = $wgUser->getOption( 'underline' ) ) < 2 ) { |
582 | | - $underline = $undopt ? 'underline' : 'none'; |
583 | | - $s .= "a { text-decoration: $underline; }\n"; |
584 | | - } |
585 | | - |
586 | | - if ( $wgUser->getOption( 'highlightbroken' ) ) { |
587 | | - $s .= "a.new, #quickbar a.new { color: #CC2200; }\n"; |
588 | | - } else { |
589 | | - $s .= <<<CSS |
590 | | -a.new, #quickbar a.new, |
591 | | -a.stub, #quickbar a.stub { |
592 | | - color: inherit; |
593 | | -} |
594 | | -a.new:after, #quickbar a.new:after { |
595 | | - content: "?"; |
596 | | - color: #CC2200; |
597 | | -} |
598 | | -a.stub:after, #quickbar a.stub:after { |
599 | | - content: "!"; |
600 | | - color: #772233; |
601 | | -} |
602 | | -CSS; |
603 | | - } |
604 | | - |
605 | | - if ( $wgUser->getOption( 'justify' ) ) { |
606 | | - $s .= "#article, #bodyContent, #mw_content { text-align: justify; }\n"; |
607 | | - } |
608 | | - |
609 | | - if ( !$wgUser->getOption( 'showtoc' ) ) { |
610 | | - $s .= "#toc { display: none; }\n"; |
611 | | - } |
612 | | - |
613 | | - if ( !$wgUser->getOption( 'editsection' ) ) { |
614 | | - $s .= ".editsection { display: none; }\n"; |
615 | | - } |
616 | | - |
617 | | - $fontstyle = $wgUser->getOption( 'editfont' ); |
618 | | - |
619 | | - if ( $fontstyle !== 'default' ) { |
620 | | - $s .= "textarea { font-family: $fontstyle; }\n"; |
621 | | - } |
622 | | - |
623 | | - return $s; |
| 472 | + |
| 473 | + // Stub - see ResourceLoaderUserModule, CologneBlue, Simple and Standard skins override this |
| 474 | + |
| 475 | + return ''; |
624 | 476 | } |
625 | 477 | |
626 | 478 | /** |
— | — | @@ -627,7 +479,7 @@ |
628 | 480 | */ |
629 | 481 | function setupUserCss( OutputPage $out ) { |
630 | 482 | global $wgRequest, $wgUser; |
631 | | - global $wgAllowUserCss, $wgUseSiteCss, $wgSquidMaxage; |
| 483 | + global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, $wgSquidMaxage; |
632 | 484 | |
633 | 485 | wfProfileIn( __METHOD__ ); |
634 | 486 | |
— | — | @@ -643,51 +495,26 @@ |
644 | 496 | $out->addStyle( $url ); |
645 | 497 | } |
646 | 498 | |
647 | | - // If we use the site's dynamic CSS, throw that in, too |
648 | 499 | // Per-site custom styles |
649 | 500 | if ( $wgUseSiteCss ) { |
650 | 501 | $out->addModuleStyles( 'site' ); |
651 | 502 | } |
652 | 503 | |
653 | | - global $wgAllowUserCssPrefs; |
654 | | - |
655 | | - if ( $wgAllowUserCssPrefs ) { |
656 | | - if ( $wgUser->isLoggedIn() ) { |
657 | | - // Ensure that logged-in users' generated CSS isn't clobbered |
658 | | - // by anons' publicly cacheable generated CSS. |
659 | | - $siteargs['smaxage'] = '0'; |
660 | | - $siteargs['ts'] = $wgUser->mTouched; |
661 | | - } |
662 | | - |
663 | | - // Per-user styles based on preferences |
664 | | - $siteargs['gen'] = 'css'; |
665 | | - |
666 | | - if ( ( $us = $wgRequest->getVal( 'useskin', '' ) ) !== '' ) { |
667 | | - $siteargs['useskin'] = $us; |
668 | | - } |
669 | | - |
670 | | - $out->addStyle( self::makeUrl( '-', wfArrayToCGI( $siteargs ) ) ); |
671 | | - } |
672 | | - |
673 | | - // Per-user custom style pages |
674 | | - if ( $wgAllowUserCss && $wgUser->isLoggedIn() ) { |
675 | | - $action = $wgRequest->getVal( 'action' ); |
676 | | - |
677 | | - # If we're previewing the CSS page, use it |
678 | | - if ( $this->mTitle->isCssSubpage() && $this->userCanPreview( $action ) ) { |
| 504 | + // Per-user custom styles |
| 505 | + if ( $wgAllowUserCss ) { |
| 506 | + if ( $this->mTitle->isCssSubpage() && $this->userCanPreview( $wgRequest->getVal( 'action' ) ) ) { |
679 | 507 | // @FIXME: properly escape the cdata! |
680 | 508 | $out->addInlineStyle( $wgRequest->getText( 'wpTextbox1' ) ); |
681 | 509 | } else { |
682 | | - $names = array( 'common', $this->getSkinName() ); |
683 | | - foreach ( $names as $name ) { |
684 | | - $out->addStyle( self::makeUrl( |
685 | | - $this->userpage . '/' . $name . '.css', |
686 | | - 'action=raw&ctype=text/css' ) |
687 | | - ); |
688 | | - } |
| 510 | + $out->addModuleStyles( 'user' ); |
689 | 511 | } |
690 | 512 | } |
691 | 513 | |
| 514 | + // Per-user preference styles |
| 515 | + if ( $wgAllowUserCssPrefs ) { |
| 516 | + $out->addModuleStyles( 'user.preferences' ); |
| 517 | + } |
| 518 | + |
692 | 519 | wfProfileOut( __METHOD__ ); |
693 | 520 | } |
694 | 521 | |
Index: trunk/phase3/includes/ResourceLoaderModule.php |
— | — | @@ -81,8 +81,6 @@ |
82 | 82 | return $context->getDirection() === 'rtl'; |
83 | 83 | } |
84 | 84 | |
85 | | - /* Abstract Methods */ |
86 | | - |
87 | 85 | /** |
88 | 86 | * Get all JS for this module for a given language and skin. |
89 | 87 | * Includes all relevant JS except loader scripts. |
— | — | @@ -90,7 +88,10 @@ |
91 | 89 | * @param $context ResourceLoaderContext object |
92 | 90 | * @return String: JS |
93 | 91 | */ |
94 | | - public abstract function getScript( ResourceLoaderContext $context ); |
| 92 | + public function getScript( ResourceLoaderContext $context ) { |
| 93 | + // Stub, override expected |
| 94 | + return ''; |
| 95 | + } |
95 | 96 | |
96 | 97 | /** |
97 | 98 | * Get all CSS for this module for a given skin. |
— | — | @@ -98,7 +99,10 @@ |
99 | 100 | * @param $context ResourceLoaderContext object |
100 | 101 | * @return array: strings of CSS keyed by media type |
101 | 102 | */ |
102 | | - public abstract function getStyles( ResourceLoaderContext $context ); |
| 103 | + public function getStyles( ResourceLoaderContext $context ) { |
| 104 | + // Stub, override expected |
| 105 | + return ''; |
| 106 | + } |
103 | 107 | |
104 | 108 | /** |
105 | 109 | * Get the messages needed for this module. |
— | — | @@ -107,14 +111,20 @@ |
108 | 112 | * |
109 | 113 | * @return array of message keys. Keys may occur more than once |
110 | 114 | */ |
111 | | - public abstract function getMessages(); |
| 115 | + public function getMessages() { |
| 116 | + // Stub, override expected |
| 117 | + return array(); |
| 118 | + } |
112 | 119 | |
113 | 120 | /** |
114 | 121 | * Get the loader JS for this module, if set. |
115 | 122 | * |
116 | 123 | * @return Mixed: loader JS (string) or false if no custom loader set |
117 | 124 | */ |
118 | | - public abstract function getLoaderScript(); |
| 125 | + public function getLoaderScript() { |
| 126 | + // Stub, override expected |
| 127 | + return ''; |
| 128 | + } |
119 | 129 | |
120 | 130 | /** |
121 | 131 | * Get a list of modules this module depends on. |
— | — | @@ -131,8 +141,13 @@ |
132 | 142 | * loader script, see getLoaderScript() |
133 | 143 | * @return Array of module names (strings) |
134 | 144 | */ |
135 | | - public abstract function getDependencies(); |
| 145 | + public function getDependencies() { |
| 146 | + // Stub, override expected |
| 147 | + return array(); |
| 148 | + } |
136 | 149 | |
| 150 | + /* Abstract Methods */ |
| 151 | + |
137 | 152 | /** |
138 | 153 | * Get this module's last modification timestamp for a given |
139 | 154 | * combination of language, skin and debug mode flag. This is typically |
— | — | @@ -682,58 +697,65 @@ |
683 | 698 | /* Protected Members */ |
684 | 699 | |
685 | 700 | // In-object cache for modified time |
686 | | - protected $modifiedTime = null; |
| 701 | + protected $modifiedTime = array(); |
687 | 702 | |
688 | 703 | /* Abstract Protected Methods */ |
689 | 704 | |
690 | 705 | abstract protected function getPages( ResourceLoaderContext $context ); |
691 | 706 | |
692 | | - /* Protected Methods */ |
693 | | - |
694 | | - protected function getStyleCode( array $styles ) { |
695 | | - foreach ( $styles as $media => $messages ) { |
696 | | - foreach ( $messages as $i => $message ) { |
697 | | - $style = wfMsgExt( $message, 'content' ); |
698 | | - if ( !wfEmptyMsg( $message, $style ) ) { |
699 | | - $styles[$media][$i] = $style; |
| 707 | + /* Methods */ |
| 708 | + |
| 709 | + public function getScript( ResourceLoaderContext $context ) { |
| 710 | + $scripts = ''; |
| 711 | + foreach ( $this->getPages( $context ) as $page => $options ) { |
| 712 | + if ( $options['type'] === 'script' ) { |
| 713 | + $script = wfMsgExt( $page, 'content' ); |
| 714 | + $scripts .= "/* MediaWiki:$page */\n" . ( !wfEmptyMsg( $page, $script ) ? $script : '' ) . "\n"; |
| 715 | + } |
| 716 | + } |
| 717 | + return $scripts; |
| 718 | + } |
| 719 | + |
| 720 | + public function getStyles( ResourceLoaderContext $context ) { |
| 721 | + $styles = array(); |
| 722 | + foreach ( $this->getPages( $context ) as $page => $options ) { |
| 723 | + if ( $options['type'] === 'style' ) { |
| 724 | + $media = isset( $options['media'] ) ? $options['media'] : 'all'; |
| 725 | + $style = wfMsgExt( $page, 'content' ); |
| 726 | + if ( !isset( $styles[$media] ) ) { |
| 727 | + $styles[$media] = ''; |
700 | 728 | } |
| 729 | + $styles[$media] .= "/* MediaWiki:$page */\n" . ( !wfEmptyMsg( $page, $style ) ? $style : '' ) . "\n"; |
701 | 730 | } |
702 | 731 | } |
703 | | - foreach ( $styles as $media => $messages ) { |
704 | | - $styles[$media] = implode( "\n", $messages ); |
705 | | - } |
706 | 732 | return $styles; |
707 | 733 | } |
708 | | - |
709 | | - /* Methods */ |
710 | | - |
| 734 | + |
711 | 735 | public function getModifiedTime( ResourceLoaderContext $context ) { |
712 | | - if ( isset( $this->modifiedTime[$context->getHash()] ) ) { |
713 | | - return $this->modifiedTime[$context->getHash()]; |
| 736 | + $hash = $context->getHash(); |
| 737 | + if ( isset( $this->modifiedTime[$hash] ) ) { |
| 738 | + return $this->modifiedTime[$hash]; |
714 | 739 | } |
715 | | - $pages = $this->getPages( $context ); |
716 | | - foreach ( $pages as $i => $page ) { |
717 | | - $pages[$i] = Title::makeTitle( NS_MEDIAWIKI, $page ); |
| 740 | + $titles = array(); |
| 741 | + foreach ( $this->getPages( $context ) as $page => $options ) { |
| 742 | + $titles[] = Title::makeTitle( NS_MEDIAWIKI, $page ); |
718 | 743 | } |
719 | 744 | // Do batch existence check |
720 | 745 | // TODO: This would work better if page_touched were loaded by this as well |
721 | | - $lb = new LinkBatch( $pages ); |
| 746 | + $lb = new LinkBatch( $titles ); |
722 | 747 | $lb->execute(); |
723 | | - $this->modifiedTime = 1; // wfTimestamp() interprets 0 as "now" |
724 | | - foreach ( $pages as $page ) { |
725 | | - if ( $page->exists() ) { |
726 | | - $this->modifiedTime = max( $this->modifiedTime, wfTimestamp( TS_UNIX, $page->getTouched() ) ); |
| 748 | + $modifiedTime = 1; // wfTimestamp() interprets 0 as "now" |
| 749 | + foreach ( $titles as $title ) { |
| 750 | + if ( $title->exists() ) { |
| 751 | + $modifiedTime = max( $modifiedTime, wfTimestamp( TS_UNIX, $title->getTouched() ) ); |
727 | 752 | } |
728 | 753 | } |
729 | | - return $this->modifiedTime; |
| 754 | + return $this->modifiedTime[$hash] = $modifiedTime; |
730 | 755 | } |
731 | | - public function getMessages() { return array(); } |
732 | | - public function getLoaderScript() { return ''; } |
733 | | - public function getDependencies() { return array(); } |
734 | 756 | } |
735 | 757 | |
736 | 758 | /** |
737 | | - * Custom module for site customizations |
| 759 | + * Module for site customizations |
738 | 760 | */ |
739 | 761 | class ResourceLoaderSiteModule extends ResourceLoaderWikiModule { |
740 | 762 | |
— | — | @@ -742,49 +764,172 @@ |
743 | 765 | protected function getPages( ResourceLoaderContext $context ) { |
744 | 766 | global $wgHandheldStyle; |
745 | 767 | |
746 | | - // HACK: We duplicate the message names from generateUserJs() and generateUserCss here and weird things (i.e. |
747 | | - // mtime moving backwards) can happen when a MediaWiki:Something.js page is deleted |
748 | 768 | $pages = array( |
749 | | - 'Common.js', |
750 | | - 'Common.css', |
751 | | - ucfirst( $context->getSkin() ) . '.js', |
752 | | - ucfirst( $context->getSkin() ) . '.css', |
753 | | - 'Print.css', |
| 769 | + 'Common.js' => array( 'type' => 'script' ), |
| 770 | + 'Common.css' => array( 'type' => 'style' ), |
| 771 | + ucfirst( $context->getSkin() ) . '.js' => array( 'type' => 'script' ), |
| 772 | + ucfirst( $context->getSkin() ) . '.css' => array( 'type' => 'style' ), |
| 773 | + 'Print.css' => array( 'type' => 'style', 'media' => 'print' ), |
754 | 774 | ); |
755 | 775 | if ( $wgHandheldStyle ) { |
756 | | - $pages[] = 'Handheld.css'; |
| 776 | + $pages['Handheld.css'] = array( 'type' => 'style', 'media' => 'handheld' ); |
757 | 777 | } |
758 | 778 | return $pages; |
759 | 779 | } |
| 780 | +} |
760 | 781 | |
| 782 | +/** |
| 783 | + * Module for user customizations |
| 784 | + */ |
| 785 | +class ResourceLoaderUserModule extends ResourceLoaderWikiModule { |
| 786 | + |
| 787 | + /* Protected Methods */ |
| 788 | + |
| 789 | + protected function getPages( ResourceLoaderContext $context ) { |
| 790 | + global $wgAllowUserCss; |
| 791 | + |
| 792 | + if ( $context->getUser() && $wgAllowUserCss ) { |
| 793 | + $user = User::newFromName( $context->getUser() ); |
| 794 | + $userPage = $user->getUserPage()->getPrefixedText(); |
| 795 | + return array( |
| 796 | + "$userPage/common.css" => array( 'type' => 'style' ), |
| 797 | + "$userPage/" . $context->getSkin() . '.css' => array( 'type' => 'style' ), |
| 798 | + ); |
| 799 | + } |
| 800 | + return array(); |
| 801 | + } |
| 802 | +} |
| 803 | + |
| 804 | +/** |
| 805 | + * Module for user preference customizations |
| 806 | + */ |
| 807 | +class ResourceLoaderUserPreferencesModule extends ResourceLoaderModule { |
| 808 | + |
| 809 | + /* Protected Members */ |
| 810 | + |
| 811 | + protected $modifiedTime = array(); |
| 812 | + |
761 | 813 | /* Methods */ |
762 | 814 | |
763 | | - public function getScript( ResourceLoaderContext $context ) { |
764 | | - return Skin::newFromKey( $context->getSkin() )->generateUserJs(); |
| 815 | + public function getModifiedTime( ResourceLoaderContext $context ) { |
| 816 | + $hash = $context->getHash(); |
| 817 | + if ( isset( $this->modifiedTime[$hash] ) ) { |
| 818 | + return $this->modifiedTime[$hash]; |
| 819 | + } |
| 820 | + $user = User::newFromName( $context->getUser() ); |
| 821 | + return $this->modifiedTime[$hash] = $user->getTouched(); |
765 | 822 | } |
766 | 823 | |
767 | 824 | public function getStyles( ResourceLoaderContext $context ) { |
768 | | - global $wgHandheldStyle; |
769 | | - $styles = array( |
770 | | - 'all' => array( 'Common.css', $context->getSkin() . '.css' ), |
771 | | - 'print' => array( 'Print.css' ), |
772 | | - ); |
773 | | - if ( $wgHandheldStyle ) { |
774 | | - $sources['handheld'] = array( 'Handheld.css' ); |
| 825 | + global $wgAllowUserCssPrefs; |
| 826 | + if ( $wgAllowUserCssPrefs ) { |
| 827 | + $user = User::newFromName( $context->getUser() ); |
| 828 | + $rules = array(); |
| 829 | + if ( ( $underline = $user->getOption( 'underline' ) ) < 2 ) { |
| 830 | + $rules[] = "a { text-decoration: " . ( $underline ? 'underline' : 'none' ) . "; }"; |
| 831 | + } |
| 832 | + if ( $user->getOption( 'highlightbroken' ) ) { |
| 833 | + $rules[] = "a.new, #quickbar a.new { color: #CC2200; }\n"; |
| 834 | + } else { |
| 835 | + $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }"; |
| 836 | + $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #CC2200; }"; |
| 837 | + $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }"; |
| 838 | + } |
| 839 | + if ( $user->getOption( 'justify' ) ) { |
| 840 | + $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n"; |
| 841 | + } |
| 842 | + if ( !$user->getOption( 'showtoc' ) ) { |
| 843 | + $rules[] = "#toc { display: none; }\n"; |
| 844 | + } |
| 845 | + if ( !$user->getOption( 'editsection' ) ) { |
| 846 | + $rules[] = ".editsection { display: none; }\n"; |
| 847 | + } |
| 848 | + if ( ( $fontstyle = $user->getOption( 'editfont' ) ) !== 'default' ) { |
| 849 | + $rules[] = "textarea { font-family: $fontstyle; }\n"; |
| 850 | + } |
| 851 | + return array( 'all' => implode( "\n", $rules ) ); |
775 | 852 | } |
776 | | - return $this->getStyleCode( $styles ); |
| 853 | + return array(); |
777 | 854 | } |
| 855 | + |
| 856 | + public function getFlip( $context ) { |
| 857 | + global $wgContLang; |
| 858 | + |
| 859 | + return $wgContLang->getDir() !== $context->getDirection(); |
| 860 | + } |
778 | 861 | } |
779 | 862 | |
780 | 863 | class ResourceLoaderStartUpModule extends ResourceLoaderModule { |
781 | 864 | /* Protected Members */ |
782 | 865 | |
783 | | - protected $modifiedTime = null; |
| 866 | + protected $modifiedTime = array(); |
784 | 867 | |
| 868 | + /* Protected Methods */ |
| 869 | + |
| 870 | + protected function getConfig( $context ) { |
| 871 | + global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension, $wgArticlePath, $wgScriptPath, $wgServer, |
| 872 | + $wgContLang, $wgBreakFrames, $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgAjaxWatch, $wgVersion, |
| 873 | + $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest, $wgSitename, $wgFileExtensions; |
| 874 | + |
| 875 | + // Pre-process information |
| 876 | + $separatorTransTable = $wgContLang->separatorTransformTable(); |
| 877 | + $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); |
| 878 | + $compactSeparatorTransTable = array( |
| 879 | + implode( "\t", array_keys( $separatorTransTable ) ), |
| 880 | + implode( "\t", $separatorTransTable ), |
| 881 | + ); |
| 882 | + $digitTransTable = $wgContLang->digitTransformTable(); |
| 883 | + $digitTransTable = $digitTransTable ? $digitTransTable : array(); |
| 884 | + $compactDigitTransTable = array( |
| 885 | + implode( "\t", array_keys( $digitTransTable ) ), |
| 886 | + implode( "\t", $digitTransTable ), |
| 887 | + ); |
| 888 | + $mainPage = Title::newMainPage(); |
| 889 | + |
| 890 | + // Build list of variables |
| 891 | + $vars = array( |
| 892 | + 'wgLoadScript' => $wgLoadScript, |
| 893 | + 'debug' => $context->getDebug(), |
| 894 | + 'skin' => $context->getSkin(), |
| 895 | + 'stylepath' => $wgStylePath, |
| 896 | + 'wgUrlProtocols' => wfUrlProtocols(), |
| 897 | + 'wgArticlePath' => $wgArticlePath, |
| 898 | + 'wgScriptPath' => $wgScriptPath, |
| 899 | + 'wgScriptExtension' => $wgScriptExtension, |
| 900 | + 'wgScript' => $wgScript, |
| 901 | + 'wgVariantArticlePath' => $wgVariantArticlePath, |
| 902 | + 'wgActionPaths' => $wgActionPaths, |
| 903 | + 'wgServer' => $wgServer, |
| 904 | + 'wgUserLanguage' => $context->getLanguage(), |
| 905 | + 'wgContentLanguage' => $wgContLang->getCode(), |
| 906 | + 'wgBreakFrames' => $wgBreakFrames, |
| 907 | + 'wgVersion' => $wgVersion, |
| 908 | + 'wgEnableAPI' => $wgEnableAPI, |
| 909 | + 'wgEnableWriteAPI' => $wgEnableWriteAPI, |
| 910 | + 'wgSeparatorTransformTable' => $compactSeparatorTransTable, |
| 911 | + 'wgDigitTransformTable' => $compactDigitTransTable, |
| 912 | + 'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null, |
| 913 | + 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(), |
| 914 | + 'wgNamespaceIds' => $wgContLang->getNamespaceIds(), |
| 915 | + 'wgSiteName' => $wgSitename, |
| 916 | + 'wgFileExtensions' => $wgFileExtensions, |
| 917 | + ); |
| 918 | + if ( $wgContLang->hasVariants() ) { |
| 919 | + $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); |
| 920 | + } |
| 921 | + if ( $wgUseAjax && $wgEnableMWSuggest ) { |
| 922 | + $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate(); |
| 923 | + $vars['wgDBname'] = $wgDBname; |
| 924 | + $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $wgUser ); |
| 925 | + } |
| 926 | + |
| 927 | + return $vars; |
| 928 | + } |
| 929 | + |
785 | 930 | /* Methods */ |
786 | 931 | |
787 | 932 | public function getScript( ResourceLoaderContext $context ) { |
788 | | - global $IP; |
| 933 | + global $IP, $wgStylePath, $wgLoadScript; |
789 | 934 | |
790 | 935 | $scripts = file_get_contents( "$IP/resources/startup.js" ); |
791 | 936 | |
— | — | @@ -792,9 +937,7 @@ |
793 | 938 | // Get all module registrations |
794 | 939 | $registration = ResourceLoader::getModuleRegistrations( $context ); |
795 | 940 | // Build configuration |
796 | | - $config = FormatJson::encode( |
797 | | - array( 'server' => $context->getServer(), 'debug' => $context->getDebug() ) |
798 | | - ); |
| 941 | + $config = FormatJson::encode( $this->getConfig( $context ) ); |
799 | 942 | // Add a well-known start-up function |
800 | 943 | $scripts .= "window.startUp = function() { $registration mediaWiki.config.set( $config ); };"; |
801 | 944 | // Build load query for jquery and mediawiki modules |
— | — | @@ -814,7 +957,7 @@ |
815 | 958 | ); |
816 | 959 | |
817 | 960 | // Build HTML code for loading jquery and mediawiki modules |
818 | | - $loadScript = Html::linkedScript( $context->getServer() . "?$query" ); |
| 961 | + $loadScript = Html::linkedScript( "$wgLoadScript?$query" ); |
819 | 962 | // Add code to add jquery and mediawiki loading code; only if the current client is compatible |
820 | 963 | $scripts .= "if ( isCompatible() ) { document.write( '$loadScript' ); }"; |
821 | 964 | // Delete the compatible function - it's not needed anymore |
— | — | @@ -827,14 +970,15 @@ |
828 | 971 | public function getModifiedTime( ResourceLoaderContext $context ) { |
829 | 972 | global $IP; |
830 | 973 | |
831 | | - if ( !is_null( $this->modifiedTime ) ) { |
832 | | - return $this->modifiedTime; |
| 974 | + $hash = $context->getHash(); |
| 975 | + if ( isset( $this->modifiedTime[$hash] ) ) { |
| 976 | + return $this->modifiedTime[$hash]; |
833 | 977 | } |
834 | | - |
835 | | - // HACK getHighestModifiedTime() calls this function, so protect against infinite recursion |
836 | | - $this->modifiedTime = filemtime( "$IP/resources/startup.js" ); |
837 | | - $this->modifiedTime = ResourceLoader::getHighestModifiedTime( $context ); |
838 | | - return $this->modifiedTime; |
| 978 | + $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" ); |
| 979 | + // ATTENTION!: Because of the line above, this is not going to cause infinite recursion - think carefully |
| 980 | + // before making changes to this code! |
| 981 | + $this->modifiedTime[$hash] = ResourceLoader::getHighestModifiedTime( $context ); |
| 982 | + return $this->modifiedTime[$hash]; |
839 | 983 | } |
840 | 984 | |
841 | 985 | public function getClientMaxage() { |
— | — | @@ -845,14 +989,9 @@ |
846 | 990 | return 300; // 5 minutes |
847 | 991 | } |
848 | 992 | |
849 | | - public function getStyles( ResourceLoaderContext $context ) { return array(); } |
850 | | - |
851 | 993 | public function getFlip( $context ) { |
852 | 994 | global $wgContLang; |
853 | 995 | |
854 | 996 | return $wgContLang->getDir() !== $context->getDirection(); |
855 | 997 | } |
856 | | - public function getMessages() { return array(); } |
857 | | - public function getLoaderScript() { return ''; } |
858 | | - public function getDependencies() { return array(); } |
859 | 998 | } |
Index: trunk/phase3/load.php |
— | — | @@ -45,7 +45,7 @@ |
46 | 46 | } |
47 | 47 | |
48 | 48 | // Respond to resource loading request |
49 | | -ResourceLoader::respond( new ResourceLoaderContext( $wgRequest, $wgServer . $wgScriptPath . '/load.php' ) ); |
| 49 | +ResourceLoader::respond( new ResourceLoaderContext( $wgRequest ) ); |
50 | 50 | |
51 | 51 | wfProfileOut( 'load.php' ); |
52 | 52 | wfLogProfilingData(); |
Index: trunk/phase3/resources/Resources.php |
— | — | @@ -6,6 +6,8 @@ |
7 | 7 | |
8 | 8 | 'site' => new ResourceLoaderSiteModule, |
9 | 9 | 'startup' => new ResourceLoaderStartUpModule, |
| 10 | + 'user' => new ResourceLoaderUserModule, |
| 11 | + 'user.preferences' => new ResourceLoaderUserPreferencesModule, |
10 | 12 | |
11 | 13 | /* Skins */ |
12 | 14 | |
— | — | @@ -356,6 +358,7 @@ |
357 | 359 | 'mediawiki.legacy.mwsuggest' => new ResourceLoaderFileModule( array( |
358 | 360 | 'scripts' => 'skins/common/mwsuggest.js', |
359 | 361 | 'dependencies' => 'mediawiki.legacy.wikibits', |
| 362 | + 'messages' => array( 'search-mwsuggest-enabled', 'search-mwsuggest-disabled' ), |
360 | 363 | ) ), |
361 | 364 | 'mediawiki.legacy.password' => new ResourceLoaderFileModule( array( |
362 | 365 | 'scripts' => 'skins/common/password.js', |
Index: trunk/phase3/resources/mediawiki/mediawiki.js |
— | — | @@ -469,7 +469,7 @@ |
470 | 470 | var html = ''; |
471 | 471 | for ( var r = 0; r < requests.length; r++ ) { |
472 | 472 | // Build out the HTML |
473 | | - var src = mediaWiki.config.get( 'server' ) + '?' + $.param( requests[r] ); |
| 473 | + var src = mediaWiki.config.get( 'wgLoadScript' ) + '?' + $.param( requests[r] ); |
474 | 474 | html += '<script type="text/javascript" src="' + src + '"></script>'; |
475 | 475 | } |
476 | 476 | return html; |