MediaWiki  REL1_23
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 getConfig( $context ) {
00040 
00041         $hash = $context->getHash();
00042         if ( isset( $this->configVars[$hash] ) ) {
00043             return $this->configVars[$hash];
00044         }
00045 
00046         global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
00047             $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
00048             $wgVariantArticlePath, $wgActionPaths, $wgVersion,
00049             $wgEnableAPI, $wgEnableWriteAPI, $wgDBname,
00050             $wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
00051             $wgCookiePrefix, $wgResourceLoaderMaxQueryLength,
00052             $wgResourceLoaderStorageEnabled, $wgResourceLoaderStorageVersion,
00053             $wgSearchType;
00054 
00055         $mainPage = Title::newMainPage();
00056 
00062         $namespaceIds = $wgContLang->getNamespaceIds();
00063         $caseSensitiveNamespaces = array();
00064         foreach ( MWNamespace::getCanonicalNamespaces() as $index => $name ) {
00065             $namespaceIds[$wgContLang->lc( $name )] = $index;
00066             if ( !MWNamespace::isCapitalized( $index ) ) {
00067                 $caseSensitiveNamespaces[] = $index;
00068             }
00069         }
00070 
00071         // Build list of variables
00072         $vars = array(
00073             'wgLoadScript' => $wgLoadScript,
00074             'debug' => $context->getDebug(),
00075             'skin' => $context->getSkin(),
00076             'stylepath' => $wgStylePath,
00077             'wgUrlProtocols' => wfUrlProtocols(),
00078             'wgArticlePath' => $wgArticlePath,
00079             'wgScriptPath' => $wgScriptPath,
00080             'wgScriptExtension' => $wgScriptExtension,
00081             'wgScript' => $wgScript,
00082             'wgSearchType' => $wgSearchType,
00083             'wgVariantArticlePath' => $wgVariantArticlePath,
00084             // Force object to avoid "empty" associative array from
00085             // becoming [] instead of {} in JS (bug 34604)
00086             'wgActionPaths' => (object)$wgActionPaths,
00087             'wgServer' => $wgServer,
00088             'wgUserLanguage' => $context->getLanguage(),
00089             'wgContentLanguage' => $wgContLang->getCode(),
00090             'wgVersion' => $wgVersion,
00091             'wgEnableAPI' => $wgEnableAPI,
00092             'wgEnableWriteAPI' => $wgEnableWriteAPI,
00093             'wgMainPageTitle' => $mainPage->getPrefixedText(),
00094             'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
00095             'wgNamespaceIds' => $namespaceIds,
00096             'wgContentNamespaces' => MWNamespace::getContentNamespaces(),
00097             'wgSiteName' => $wgSitename,
00098             'wgFileExtensions' => array_values( array_unique( $wgFileExtensions ) ),
00099             'wgDBname' => $wgDBname,
00100             // This sucks, it is only needed on Special:Upload, but I could
00101             // not find a way to add vars only for a certain module
00102             'wgFileCanRotate' => BitmapHandler::canRotate(),
00103             'wgAvailableSkins' => Skin::getSkinNames(),
00104             'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
00105             // MediaWiki sets cookies to have this prefix by default
00106             'wgCookiePrefix' => $wgCookiePrefix,
00107             'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
00108             'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
00109             'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
00110             'wgResourceLoaderStorageVersion' => $wgResourceLoaderStorageVersion,
00111             'wgResourceLoaderStorageEnabled' => $wgResourceLoaderStorageEnabled,
00112         );
00113 
00114         wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
00115 
00116         $this->configVars[$hash] = $vars;
00117         return $this->configVars[$hash];
00118     }
00119 
00126     public static function getModuleRegistrations( ResourceLoaderContext $context ) {
00127         global $wgCacheEpoch;
00128         wfProfileIn( __METHOD__ );
00129 
00130         $resourceLoader = $context->getResourceLoader();
00131         $target = $context->getRequest()->getVal( 'target', 'desktop' );
00132 
00133         $out = '';
00134         $registryData = array();
00135 
00136         // Get registry data
00137         foreach ( $resourceLoader->getModuleNames() as $name ) {
00138             $module = $resourceLoader->getModule( $name );
00139             $moduleTargets = $module->getTargets();
00140             if ( !in_array( $target, $moduleTargets ) ) {
00141                 continue;
00142             }
00143 
00144             // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always
00145             // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
00146             $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
00147             $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
00148 
00149             // FIXME: Convert to numbers, wfTimestamp always gives us stings, even for TS_UNIX
00150 
00151             $registryData[ $name ] = array(
00152                 'version' => $mtime,
00153                 'dependencies' => $module->getDependencies(),
00154                 'group' => $module->getGroup(),
00155                 'source' => $module->getSource(),
00156                 'loader' => $module->getLoaderScript(),
00157             );
00158         }
00159 
00160         // Register sources
00161         $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
00162 
00163         // Concatenate module loader scripts and figure out the different call
00164         // signatures for mw.loader.register
00165         $registrations = array();
00166         foreach ( $registryData as $name => $data ) {
00167             if ( $data['loader'] !== false ) {
00168                 $out .= ResourceLoader::makeCustomLoaderScript(
00169                     $name,
00170                     wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ),
00171                     $data['dependencies'],
00172                     $data['group'],
00173                     $data['source'],
00174                     $data['loader']
00175                 );
00176                 continue;
00177             }
00178 
00179             if (
00180                 !count( $data['dependencies'] ) &&
00181                 $data['group'] === null &&
00182                 $data['source'] === 'local'
00183             ) {
00184                 // Modules without dependencies, a group or a foreign source;
00185                 // call mw.loader.register(name, timestamp)
00186                 $registrations[] = array( $name, $data['version'] );
00187             } elseif ( $data['group'] === null && $data['source'] === 'local' ) {
00188                 // Modules with dependencies but no group or foreign source;
00189                 // call mw.loader.register(name, timestamp, dependencies)
00190                 $registrations[] = array( $name, $data['version'], $data['dependencies'] );
00191             } elseif ( $data['source'] === 'local' ) {
00192                 // Modules with a group but no foreign source;
00193                 // call mw.loader.register(name, timestamp, dependencies, group)
00194                 $registrations[] = array(
00195                     $name,
00196                     $data['version'],
00197                     $data['dependencies'],
00198                     $data['group']
00199                 );
00200             } else {
00201                 // Modules with a foreign source;
00202                 // call mw.loader.register(name, timestamp, dependencies, group, source)
00203                 $registrations[] = array(
00204                     $name,
00205                     $data['version'],
00206                     $data['dependencies'],
00207                     $data['group'],
00208                     $data['source']
00209                 );
00210             }
00211         }
00212 
00213         // Register modules
00214         $out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
00215 
00216         wfProfileOut( __METHOD__ );
00217         return $out;
00218     }
00219 
00220     /* Methods */
00221 
00225     public function isRaw() {
00226         return true;
00227     }
00228 
00238     public static function getStartupModulesUrl( ResourceLoaderContext $context ) {
00239         // The core modules:
00240         $moduleNames = array( 'jquery', 'mediawiki' );
00241         wfRunHooks( 'ResourceLoaderGetStartupModules', array( &$moduleNames ), '1.23' );
00242 
00243         // Get the latest version
00244         $loader = $context->getResourceLoader();
00245         $version = 0;
00246         foreach ( $moduleNames as $moduleName ) {
00247             $version = max( $version,
00248                 $loader->getModule( $moduleName )->getModifiedTime( $context )
00249             );
00250         }
00251 
00252         $query = array(
00253             'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
00254             'only' => 'scripts',
00255             'lang' => $context->getLanguage(),
00256             'skin' => $context->getSkin(),
00257             'debug' => $context->getDebug() ? 'true' : 'false',
00258             'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
00259         );
00260         // Ensure uniform query order
00261         ksort( $query );
00262         return wfAppendQuery( wfScript( 'load' ), $query );
00263     }
00264 
00269     public function getScript( ResourceLoaderContext $context ) {
00270         global $IP, $wgLegacyJavaScriptGlobals;
00271 
00272         $out = file_get_contents( "$IP/resources/src/startup.js" );
00273         if ( $context->getOnly() === 'scripts' ) {
00274 
00275             // Startup function
00276             $configuration = $this->getConfig( $context );
00277             $registrations = self::getModuleRegistrations( $context );
00278             // Fix indentation
00279             $registrations = str_replace( "\n", "\n\t", trim( $registrations ) );
00280             $out .= "var startUp = function () {\n" .
00281                 "\tmw.config = new " .
00282                 Xml::encodeJsCall( 'mw.Map', array( $wgLegacyJavaScriptGlobals ) ) . "\n" .
00283                 "\t$registrations\n" .
00284                 "\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
00285                 "};\n";
00286 
00287             // Conditional script injection
00288             $scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) );
00289             $out .= "if ( isCompatible() ) {\n" .
00290                 "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
00291                 "}";
00292         }
00293 
00294         return $out;
00295     }
00296 
00300     public function supportsURLLoading() {
00301         return false;
00302     }
00303 
00308     public function getModifiedTime( ResourceLoaderContext $context ) {
00309         global $IP, $wgCacheEpoch;
00310 
00311         $hash = $context->getHash();
00312         if ( isset( $this->modifiedTime[$hash] ) ) {
00313             return $this->modifiedTime[$hash];
00314         }
00315 
00316         // Call preloadModuleInfo() on ALL modules as we're about
00317         // to call getModifiedTime() on all of them
00318         $loader = $context->getResourceLoader();
00319         $loader->preloadModuleInfo( $loader->getModuleNames(), $context );
00320 
00321         $time = max(
00322             wfTimestamp( TS_UNIX, $wgCacheEpoch ),
00323             filemtime( "$IP/resources/src/startup.js" ),
00324             $this->getHashMtime( $context )
00325         );
00326 
00327         // ATTENTION!: Because of the line below, this is not going to cause
00328         // infinite recursion - think carefully before making changes to this
00329         // code!
00330         // Pre-populate modifiedTime with something because the the loop over
00331         // all modules below includes the the startup module (this module).
00332         $this->modifiedTime[$hash] = 1;
00333 
00334         foreach ( $loader->getModuleNames() as $name ) {
00335             $module = $loader->getModule( $name );
00336             $time = max( $time, $module->getModifiedTime( $context ) );
00337         }
00338 
00339         $this->modifiedTime[$hash] = $time;
00340         return $this->modifiedTime[$hash];
00341     }
00342 
00351     public function getModifiedHash( ResourceLoaderContext $context ) {
00352         global $wgLegacyJavaScriptGlobals;
00353 
00354         $data = array(
00355             'vars' => $this->getConfig( $context ),
00356             'wgLegacyJavaScriptGlobals' => $wgLegacyJavaScriptGlobals,
00357         );
00358 
00359         return md5( serialize( $data ) );
00360     }
00361 
00365     public function getGroup() {
00366         return 'startup';
00367     }
00368 }