MediaWiki
REL1_24
|
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 }