MediaWiki  REL1_24
ResourceLoaderStartUpModule.php
Go to the documentation of this file.
00001 <?php
00025 class ResourceLoaderStartUpModule extends ResourceLoaderModule {
00026 
00027     /* Protected Members */
00028 
00029     protected $modifiedTime = array();
00030     protected $configVars = array();
00031     protected $targets = array( 'desktop', 'mobile' );
00032 
00033     /* Protected Methods */
00034 
00039     protected function getConfigSettings( $context ) {
00040 
00041         $hash = $context->getHash();
00042         if ( isset( $this->configVars[$hash] ) ) {
00043             return $this->configVars[$hash];
00044         }
00045 
00046         global $wgContLang;
00047 
00048         $mainPage = Title::newMainPage();
00049 
00055         $namespaceIds = $wgContLang->getNamespaceIds();
00056         $caseSensitiveNamespaces = array();
00057         foreach ( MWNamespace::getCanonicalNamespaces() as $index => $name ) {
00058             $namespaceIds[$wgContLang->lc( $name )] = $index;
00059             if ( !MWNamespace::isCapitalized( $index ) ) {
00060                 $caseSensitiveNamespaces[] = $index;
00061             }
00062         }
00063 
00064         $conf = $this->getConfig();
00065         // Build list of variables
00066         $vars = array(
00067             'wgLoadScript' => wfScript( 'load' ),
00068             'debug' => $context->getDebug(),
00069             'skin' => $context->getSkin(),
00070             'stylepath' => $conf->get( 'StylePath' ),
00071             'wgUrlProtocols' => wfUrlProtocols(),
00072             'wgArticlePath' => $conf->get( 'ArticlePath' ),
00073             'wgScriptPath' => $conf->get( 'ScriptPath' ),
00074             'wgScriptExtension' => $conf->get( 'ScriptExtension' ),
00075             'wgScript' => wfScript(),
00076             'wgSearchType' => $conf->get( 'SearchType' ),
00077             'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ),
00078             // Force object to avoid "empty" associative array from
00079             // becoming [] instead of {} in JS (bug 34604)
00080             'wgActionPaths' => (object)$conf->get( 'ActionPaths' ),
00081             'wgServer' => $conf->get( 'Server' ),
00082             'wgServerName' => $conf->get( 'ServerName' ),
00083             'wgUserLanguage' => $context->getLanguage(),
00084             'wgContentLanguage' => $wgContLang->getCode(),
00085             'wgVersion' => $conf->get( 'Version' ),
00086             'wgEnableAPI' => $conf->get( 'EnableAPI' ),
00087             'wgEnableWriteAPI' => $conf->get( 'EnableWriteAPI' ),
00088             'wgMainPageTitle' => $mainPage->getPrefixedText(),
00089             'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
00090             'wgNamespaceIds' => $namespaceIds,
00091             'wgContentNamespaces' => MWNamespace::getContentNamespaces(),
00092             'wgSiteName' => $conf->get( 'Sitename' ),
00093             'wgFileExtensions' => array_values( array_unique( $conf->get( 'FileExtensions' ) ) ),
00094             'wgDBname' => $conf->get( 'DBname' ),
00095             // This sucks, it is only needed on Special:Upload, but I could
00096             // not find a way to add vars only for a certain module
00097             'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
00098             'wgAvailableSkins' => Skin::getSkinNames(),
00099             'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
00100             // MediaWiki sets cookies to have this prefix by default
00101             'wgCookiePrefix' => $conf->get( 'CookiePrefix' ),
00102             'wgCookieDomain' => $conf->get( 'CookieDomain' ),
00103             'wgCookiePath' => $conf->get( 'CookiePath' ),
00104             'wgCookieExpiration' => $conf->get( 'CookieExpiration' ),
00105             'wgResourceLoaderMaxQueryLength' => $conf->get( 'ResourceLoaderMaxQueryLength' ),
00106             'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
00107             'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
00108             'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
00109             'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
00110         );
00111 
00112         wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
00113 
00114         $this->configVars[$hash] = $vars;
00115         return $this->configVars[$hash];
00116     }
00117 
00125     protected static function getImplicitDependencies( array $registryData, $moduleName ) {
00126         static $dependencyCache = array();
00127 
00128         // The list of implicit dependencies won't be altered, so we can
00129         // cache them without having to worry.
00130         if ( !isset( $dependencyCache[$moduleName] ) ) {
00131 
00132             if ( !isset( $registryData[$moduleName] ) ) {
00133                 // Dependencies may not exist
00134                 $dependencyCache[$moduleName] = array();
00135             } else {
00136                 $data = $registryData[$moduleName];
00137                 $dependencyCache[$moduleName] = $data['dependencies'];
00138 
00139                 foreach ( $data['dependencies'] as $dependency ) {
00140                     // Recursively get the dependencies of the dependencies
00141                     $dependencyCache[$moduleName] = array_merge(
00142                         $dependencyCache[$moduleName],
00143                         self::getImplicitDependencies( $registryData, $dependency )
00144                     );
00145                 }
00146             }
00147         }
00148 
00149         return $dependencyCache[$moduleName];
00150     }
00151 
00171     public static function compileUnresolvedDependencies( array &$registryData ) {
00172         foreach ( $registryData as $name => &$data ) {
00173             if ( $data['loader'] !== false ) {
00174                 continue;
00175             }
00176             $dependencies = $data['dependencies'];
00177             foreach ( $data['dependencies'] as $dependency ) {
00178                 $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
00179                 $dependencies = array_diff( $dependencies, $implicitDependencies );
00180             }
00181             // Rebuild keys
00182             $data['dependencies'] = array_values( $dependencies );
00183         }
00184     }
00185 
00186 
00193     public function getModuleRegistrations( ResourceLoaderContext $context ) {
00194         wfProfileIn( __METHOD__ );
00195 
00196         $resourceLoader = $context->getResourceLoader();
00197         $target = $context->getRequest()->getVal( 'target', 'desktop' );
00198 
00199         $out = '';
00200         $registryData = array();
00201 
00202         // Get registry data
00203         foreach ( $resourceLoader->getModuleNames() as $name ) {
00204             $module = $resourceLoader->getModule( $name );
00205             $moduleTargets = $module->getTargets();
00206             if ( !in_array( $target, $moduleTargets ) ) {
00207                 continue;
00208             }
00209 
00210             if ( $module->isRaw() ) {
00211                 // Don't register "raw" modules (like 'jquery' and 'mediawiki') client-side because
00212                 // depending on them is illegal anyway and would only lead to them being reloaded
00213                 // causing any state to be lost (like jQuery plugins, mw.config etc.)
00214                 continue;
00215             }
00216 
00217             // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always
00218             // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
00219             $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
00220             $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ) );
00221 
00222             // FIXME: Convert to numbers, wfTimestamp always gives us stings, even for TS_UNIX
00223 
00224             $skipFunction = $module->getSkipFunction();
00225             if ( $skipFunction !== null && !ResourceLoader::inDebugMode() ) {
00226                 $skipFunction = $resourceLoader->filter( 'minify-js',
00227                     $skipFunction,
00228                     // There will potentially be lots of these little string in the registrations
00229                     // manifest, we don't want to blow up the startup module with
00230                     // "/* cache key: ... */" all over it in non-debug mode.
00231                     /* cacheReport = */ false
00232                 );
00233             }
00234 
00235             $registryData[$name] = array(
00236                 'version' => $mtime,
00237                 'dependencies' => $module->getDependencies(),
00238                 'group' => $module->getGroup(),
00239                 'source' => $module->getSource(),
00240                 'loader' => $module->getLoaderScript(),
00241                 'skip' => $skipFunction,
00242             );
00243         }
00244 
00245         self::compileUnresolvedDependencies( $registryData );
00246 
00247         // Register sources
00248         $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
00249 
00250         // Concatenate module loader scripts and figure out the different call
00251         // signatures for mw.loader.register
00252         $registrations = array();
00253         foreach ( $registryData as $name => $data ) {
00254             if ( $data['loader'] !== false ) {
00255                 $out .= ResourceLoader::makeCustomLoaderScript(
00256                     $name,
00257                     wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ),
00258                     $data['dependencies'],
00259                     $data['group'],
00260                     $data['source'],
00261                     $data['loader']
00262                 );
00263                 continue;
00264             }
00265 
00266             if (
00267                 !count( $data['dependencies'] ) &&
00268                 $data['group'] === null &&
00269                 $data['source'] === 'local' &&
00270                 $data['skip'] === null
00271             ) {
00272                 // Modules with no dependencies, group, foreign source or skip function;
00273                 // call mw.loader.register(name, timestamp)
00274                 $registrations[] = array( $name, $data['version'] );
00275             } elseif (
00276                 $data['group'] === null &&
00277                 $data['source'] === 'local' &&
00278                 $data['skip'] === null
00279             ) {
00280                 // Modules with dependencies but no group, foreign source or skip function;
00281                 // call mw.loader.register(name, timestamp, dependencies)
00282                 $registrations[] = array( $name, $data['version'], $data['dependencies'] );
00283             } elseif (
00284                 $data['source'] === 'local' &&
00285                 $data['skip'] === null
00286             ) {
00287                 // Modules with a group but no foreign source or skip function;
00288                 // call mw.loader.register(name, timestamp, dependencies, group)
00289                 $registrations[] = array(
00290                     $name,
00291                     $data['version'],
00292                     $data['dependencies'],
00293                     $data['group']
00294                 );
00295             } elseif ( $data['skip'] === null ) {
00296                 // Modules with a foreign source but no skip function;
00297                 // call mw.loader.register(name, timestamp, dependencies, group, source)
00298                 $registrations[] = array(
00299                     $name,
00300                     $data['version'],
00301                     $data['dependencies'],
00302                     $data['group'],
00303                     $data['source']
00304                 );
00305             } else {
00306                 // Modules with a skip function;
00307                 // call mw.loader.register(name, timestamp, dependencies, group, source, skip)
00308                 $registrations[] = array(
00309                     $name,
00310                     $data['version'],
00311                     $data['dependencies'],
00312                     $data['group'],
00313                     $data['source'],
00314                     $data['skip']
00315                 );
00316             }
00317         }
00318 
00319         // Register modules
00320         $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
00321 
00322         wfProfileOut( __METHOD__ );
00323         return $out;
00324     }
00325 
00326     /* Methods */
00327 
00331     public function isRaw() {
00332         return true;
00333     }
00334 
00340     public static function getStartupModules() {
00341         return array( 'jquery', 'mediawiki' );
00342     }
00343 
00353     public static function getStartupModulesUrl( ResourceLoaderContext $context ) {
00354         $moduleNames = self::getStartupModules();
00355 
00356         // Get the latest version
00357         $loader = $context->getResourceLoader();
00358         $version = 0;
00359         foreach ( $moduleNames as $moduleName ) {
00360             $version = max( $version,
00361                 $loader->getModule( $moduleName )->getModifiedTime( $context )
00362             );
00363         }
00364 
00365         $query = array(
00366             'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
00367             'only' => 'scripts',
00368             'lang' => $context->getLanguage(),
00369             'skin' => $context->getSkin(),
00370             'debug' => $context->getDebug() ? 'true' : 'false',
00371             'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
00372         );
00373         // Ensure uniform query order
00374         ksort( $query );
00375         return wfAppendQuery( wfScript( 'load' ), $query );
00376     }
00377 
00382     public function getScript( ResourceLoaderContext $context ) {
00383         global $IP;
00384 
00385         $out = file_get_contents( "$IP/resources/src/startup.js" );
00386         if ( $context->getOnly() === 'scripts' ) {
00387 
00388             // Startup function
00389             $configuration = $this->getConfigSettings( $context );
00390             $registrations = $this->getModuleRegistrations( $context );
00391             // Fix indentation
00392             $registrations = str_replace( "\n", "\n\t", trim( $registrations ) );
00393             $out .= "var startUp = function () {\n" .
00394                 "\tmw.config = new " .
00395                 Xml::encodeJsCall( 'mw.Map', array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" .
00396                 "\t$registrations\n" .
00397                 "\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
00398                 "};\n";
00399 
00400             // Conditional script injection
00401             $scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) );
00402             $out .= "if ( isCompatible() ) {\n" .
00403                 "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
00404                 "}";
00405         }
00406 
00407         return $out;
00408     }
00409 
00413     public function supportsURLLoading() {
00414         return false;
00415     }
00416 
00421     public function getModifiedTime( ResourceLoaderContext $context ) {
00422         global $IP;
00423 
00424         $hash = $context->getHash();
00425         if ( isset( $this->modifiedTime[$hash] ) ) {
00426             return $this->modifiedTime[$hash];
00427         }
00428 
00429         // Call preloadModuleInfo() on ALL modules as we're about
00430         // to call getModifiedTime() on all of them
00431         $loader = $context->getResourceLoader();
00432         $loader->preloadModuleInfo( $loader->getModuleNames(), $context );
00433 
00434         $time = max(
00435             wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ),
00436             filemtime( "$IP/resources/src/startup.js" ),
00437             $this->getHashMtime( $context )
00438         );
00439 
00440         // ATTENTION!: Because of the line below, this is not going to cause
00441         // infinite recursion - think carefully before making changes to this
00442         // code!
00443         // Pre-populate modifiedTime with something because the the loop over
00444         // all modules below includes the the startup module (this module).
00445         $this->modifiedTime[$hash] = 1;
00446 
00447         foreach ( $loader->getModuleNames() as $name ) {
00448             $module = $loader->getModule( $name );
00449             $time = max( $time, $module->getModifiedTime( $context ) );
00450         }
00451 
00452         $this->modifiedTime[$hash] = $time;
00453         return $this->modifiedTime[$hash];
00454     }
00455 
00464     public function getModifiedHash( ResourceLoaderContext $context ) {
00465         $data = array(
00466             'vars' => $this->getConfigSettings( $context ),
00467             'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
00468         );
00469 
00470         return md5( serialize( $data ) );
00471     }
00472 
00476     public function getGroup() {
00477         return 'startup';
00478     }
00479 }