[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Module for resource loader initialization. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @author Trevor Parscal 22 * @author Roan Kattouw 23 */ 24 25 class ResourceLoaderStartUpModule extends ResourceLoaderModule { 26 27 /* Protected Members */ 28 29 protected $modifiedTime = array(); 30 protected $configVars = array(); 31 protected $targets = array( 'desktop', 'mobile' ); 32 33 /* Protected Methods */ 34 35 /** 36 * @param ResourceLoaderContext $context 37 * @return array 38 */ 39 protected function getConfigSettings( $context ) { 40 41 $hash = $context->getHash(); 42 if ( isset( $this->configVars[$hash] ) ) { 43 return $this->configVars[$hash]; 44 } 45 46 global $wgContLang; 47 48 $mainPage = Title::newMainPage(); 49 50 /** 51 * Namespace related preparation 52 * - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces. 53 * - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive. 54 */ 55 $namespaceIds = $wgContLang->getNamespaceIds(); 56 $caseSensitiveNamespaces = array(); 57 foreach ( MWNamespace::getCanonicalNamespaces() as $index => $name ) { 58 $namespaceIds[$wgContLang->lc( $name )] = $index; 59 if ( !MWNamespace::isCapitalized( $index ) ) { 60 $caseSensitiveNamespaces[] = $index; 61 } 62 } 63 64 $conf = $this->getConfig(); 65 // Build list of variables 66 $vars = array( 67 'wgLoadScript' => wfScript( 'load' ), 68 'debug' => $context->getDebug(), 69 'skin' => $context->getSkin(), 70 'stylepath' => $conf->get( 'StylePath' ), 71 'wgUrlProtocols' => wfUrlProtocols(), 72 'wgArticlePath' => $conf->get( 'ArticlePath' ), 73 'wgScriptPath' => $conf->get( 'ScriptPath' ), 74 'wgScriptExtension' => $conf->get( 'ScriptExtension' ), 75 'wgScript' => wfScript(), 76 'wgSearchType' => $conf->get( 'SearchType' ), 77 'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ), 78 // Force object to avoid "empty" associative array from 79 // becoming [] instead of {} in JS (bug 34604) 80 'wgActionPaths' => (object)$conf->get( 'ActionPaths' ), 81 'wgServer' => $conf->get( 'Server' ), 82 'wgServerName' => $conf->get( 'ServerName' ), 83 'wgUserLanguage' => $context->getLanguage(), 84 'wgContentLanguage' => $wgContLang->getCode(), 85 'wgVersion' => $conf->get( 'Version' ), 86 'wgEnableAPI' => $conf->get( 'EnableAPI' ), 87 'wgEnableWriteAPI' => $conf->get( 'EnableWriteAPI' ), 88 'wgMainPageTitle' => $mainPage->getPrefixedText(), 89 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(), 90 'wgNamespaceIds' => $namespaceIds, 91 'wgContentNamespaces' => MWNamespace::getContentNamespaces(), 92 'wgSiteName' => $conf->get( 'Sitename' ), 93 'wgFileExtensions' => array_values( array_unique( $conf->get( 'FileExtensions' ) ) ), 94 'wgDBname' => $conf->get( 'DBname' ), 95 // This sucks, it is only needed on Special:Upload, but I could 96 // not find a way to add vars only for a certain module 97 'wgFileCanRotate' => SpecialUpload::rotationEnabled(), 98 'wgAvailableSkins' => Skin::getSkinNames(), 99 'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ), 100 // MediaWiki sets cookies to have this prefix by default 101 'wgCookiePrefix' => $conf->get( 'CookiePrefix' ), 102 'wgCookieDomain' => $conf->get( 'CookieDomain' ), 103 'wgCookiePath' => $conf->get( 'CookiePath' ), 104 'wgCookieExpiration' => $conf->get( 'CookieExpiration' ), 105 'wgResourceLoaderMaxQueryLength' => $conf->get( 'ResourceLoaderMaxQueryLength' ), 106 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces, 107 'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ), 108 'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ), 109 'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ), 110 ); 111 112 wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) ); 113 114 $this->configVars[$hash] = $vars; 115 return $this->configVars[$hash]; 116 } 117 118 /** 119 * Recursively get all explicit and implicit dependencies for to the given module. 120 * 121 * @param array $registryData 122 * @param string $moduleName 123 * @return array 124 */ 125 protected static function getImplicitDependencies( array $registryData, $moduleName ) { 126 static $dependencyCache = array(); 127 128 // The list of implicit dependencies won't be altered, so we can 129 // cache them without having to worry. 130 if ( !isset( $dependencyCache[$moduleName] ) ) { 131 132 if ( !isset( $registryData[$moduleName] ) ) { 133 // Dependencies may not exist 134 $dependencyCache[$moduleName] = array(); 135 } else { 136 $data = $registryData[$moduleName]; 137 $dependencyCache[$moduleName] = $data['dependencies']; 138 139 foreach ( $data['dependencies'] as $dependency ) { 140 // Recursively get the dependencies of the dependencies 141 $dependencyCache[$moduleName] = array_merge( 142 $dependencyCache[$moduleName], 143 self::getImplicitDependencies( $registryData, $dependency ) 144 ); 145 } 146 } 147 } 148 149 return $dependencyCache[$moduleName]; 150 } 151 152 /** 153 * Optimize the dependency tree in $this->modules and return it. 154 * 155 * The optimization basically works like this: 156 * Given we have module A with the dependencies B and C 157 * and module B with the dependency C. 158 * Now we don't have to tell the client to explicitly fetch module 159 * C as that's already included in module B. 160 * 161 * This way we can reasonably reduce the amout of module registration 162 * data send to the client. 163 * 164 * @param array &$registryData Modules keyed by name with properties: 165 * - string 'version' 166 * - array 'dependencies' 167 * - string|null 'group' 168 * - string 'source' 169 * - string|false 'loader' 170 */ 171 public static function compileUnresolvedDependencies( array &$registryData ) { 172 foreach ( $registryData as $name => &$data ) { 173 if ( $data['loader'] !== false ) { 174 continue; 175 } 176 $dependencies = $data['dependencies']; 177 foreach ( $data['dependencies'] as $dependency ) { 178 $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency ); 179 $dependencies = array_diff( $dependencies, $implicitDependencies ); 180 } 181 // Rebuild keys 182 $data['dependencies'] = array_values( $dependencies ); 183 } 184 } 185 186 187 /** 188 * Get registration code for all modules. 189 * 190 * @param ResourceLoaderContext $context 191 * @return string JavaScript code for registering all modules with the client loader 192 */ 193 public function getModuleRegistrations( ResourceLoaderContext $context ) { 194 wfProfileIn( __METHOD__ ); 195 196 $resourceLoader = $context->getResourceLoader(); 197 $target = $context->getRequest()->getVal( 'target', 'desktop' ); 198 199 $out = ''; 200 $registryData = array(); 201 202 // Get registry data 203 foreach ( $resourceLoader->getModuleNames() as $name ) { 204 $module = $resourceLoader->getModule( $name ); 205 $moduleTargets = $module->getTargets(); 206 if ( !in_array( $target, $moduleTargets ) ) { 207 continue; 208 } 209 210 if ( $module->isRaw() ) { 211 // Don't register "raw" modules (like 'jquery' and 'mediawiki') client-side because 212 // depending on them is illegal anyway and would only lead to them being reloaded 213 // causing any state to be lost (like jQuery plugins, mw.config etc.) 214 continue; 215 } 216 217 // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always 218 // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX 219 $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) ); 220 $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ) ); 221 222 // FIXME: Convert to numbers, wfTimestamp always gives us stings, even for TS_UNIX 223 224 $skipFunction = $module->getSkipFunction(); 225 if ( $skipFunction !== null && !ResourceLoader::inDebugMode() ) { 226 $skipFunction = $resourceLoader->filter( 'minify-js', 227 $skipFunction, 228 // There will potentially be lots of these little string in the registrations 229 // manifest, we don't want to blow up the startup module with 230 // "/* cache key: ... */" all over it in non-debug mode. 231 /* cacheReport = */ false 232 ); 233 } 234 235 $registryData[$name] = array( 236 'version' => $mtime, 237 'dependencies' => $module->getDependencies(), 238 'group' => $module->getGroup(), 239 'source' => $module->getSource(), 240 'loader' => $module->getLoaderScript(), 241 'skip' => $skipFunction, 242 ); 243 } 244 245 self::compileUnresolvedDependencies( $registryData ); 246 247 // Register sources 248 $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() ); 249 250 // Concatenate module loader scripts and figure out the different call 251 // signatures for mw.loader.register 252 $registrations = array(); 253 foreach ( $registryData as $name => $data ) { 254 if ( $data['loader'] !== false ) { 255 $out .= ResourceLoader::makeCustomLoaderScript( 256 $name, 257 wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ), 258 $data['dependencies'], 259 $data['group'], 260 $data['source'], 261 $data['loader'] 262 ); 263 continue; 264 } 265 266 if ( 267 !count( $data['dependencies'] ) && 268 $data['group'] === null && 269 $data['source'] === 'local' && 270 $data['skip'] === null 271 ) { 272 // Modules with no dependencies, group, foreign source or skip function; 273 // call mw.loader.register(name, timestamp) 274 $registrations[] = array( $name, $data['version'] ); 275 } elseif ( 276 $data['group'] === null && 277 $data['source'] === 'local' && 278 $data['skip'] === null 279 ) { 280 // Modules with dependencies but no group, foreign source or skip function; 281 // call mw.loader.register(name, timestamp, dependencies) 282 $registrations[] = array( $name, $data['version'], $data['dependencies'] ); 283 } elseif ( 284 $data['source'] === 'local' && 285 $data['skip'] === null 286 ) { 287 // Modules with a group but no foreign source or skip function; 288 // call mw.loader.register(name, timestamp, dependencies, group) 289 $registrations[] = array( 290 $name, 291 $data['version'], 292 $data['dependencies'], 293 $data['group'] 294 ); 295 } elseif ( $data['skip'] === null ) { 296 // Modules with a foreign source but no skip function; 297 // call mw.loader.register(name, timestamp, dependencies, group, source) 298 $registrations[] = array( 299 $name, 300 $data['version'], 301 $data['dependencies'], 302 $data['group'], 303 $data['source'] 304 ); 305 } else { 306 // Modules with a skip function; 307 // call mw.loader.register(name, timestamp, dependencies, group, source, skip) 308 $registrations[] = array( 309 $name, 310 $data['version'], 311 $data['dependencies'], 312 $data['group'], 313 $data['source'], 314 $data['skip'] 315 ); 316 } 317 } 318 319 // Register modules 320 $out .= ResourceLoader::makeLoaderRegisterScript( $registrations ); 321 322 wfProfileOut( __METHOD__ ); 323 return $out; 324 } 325 326 /* Methods */ 327 328 /** 329 * @return bool 330 */ 331 public function isRaw() { 332 return true; 333 } 334 335 /** 336 * Base modules required for the the base environment of ResourceLoader 337 * 338 * @return array 339 */ 340 public static function getStartupModules() { 341 return array( 'jquery', 'mediawiki' ); 342 } 343 344 /** 345 * Get the load URL of the startup modules. 346 * 347 * This is a helper for getScript(), but can also be called standalone, such 348 * as when generating an AppCache manifest. 349 * 350 * @param ResourceLoaderContext $context 351 * @return string 352 */ 353 public static function getStartupModulesUrl( ResourceLoaderContext $context ) { 354 $moduleNames = self::getStartupModules(); 355 356 // Get the latest version 357 $loader = $context->getResourceLoader(); 358 $version = 0; 359 foreach ( $moduleNames as $moduleName ) { 360 $version = max( $version, 361 $loader->getModule( $moduleName )->getModifiedTime( $context ) 362 ); 363 } 364 365 $query = array( 366 'modules' => ResourceLoader::makePackedModulesString( $moduleNames ), 367 'only' => 'scripts', 368 'lang' => $context->getLanguage(), 369 'skin' => $context->getSkin(), 370 'debug' => $context->getDebug() ? 'true' : 'false', 371 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version ) 372 ); 373 // Ensure uniform query order 374 ksort( $query ); 375 return wfAppendQuery( wfScript( 'load' ), $query ); 376 } 377 378 /** 379 * @param ResourceLoaderContext $context 380 * @return string 381 */ 382 public function getScript( ResourceLoaderContext $context ) { 383 global $IP; 384 385 $out = file_get_contents( "$IP/resources/src/startup.js" ); 386 if ( $context->getOnly() === 'scripts' ) { 387 388 // Startup function 389 $configuration = $this->getConfigSettings( $context ); 390 $registrations = $this->getModuleRegistrations( $context ); 391 // Fix indentation 392 $registrations = str_replace( "\n", "\n\t", trim( $registrations ) ); 393 $out .= "var startUp = function () {\n" . 394 "\tmw.config = new " . 395 Xml::encodeJsCall( 'mw.Map', array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" . 396 "\t$registrations\n" . 397 "\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) . 398 "};\n"; 399 400 // Conditional script injection 401 $scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) ); 402 $out .= "if ( isCompatible() ) {\n" . 403 "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) . 404 "}"; 405 } 406 407 return $out; 408 } 409 410 /** 411 * @return bool 412 */ 413 public function supportsURLLoading() { 414 return false; 415 } 416 417 /** 418 * @param ResourceLoaderContext $context 419 * @return array|mixed 420 */ 421 public function getModifiedTime( ResourceLoaderContext $context ) { 422 global $IP; 423 424 $hash = $context->getHash(); 425 if ( isset( $this->modifiedTime[$hash] ) ) { 426 return $this->modifiedTime[$hash]; 427 } 428 429 // Call preloadModuleInfo() on ALL modules as we're about 430 // to call getModifiedTime() on all of them 431 $loader = $context->getResourceLoader(); 432 $loader->preloadModuleInfo( $loader->getModuleNames(), $context ); 433 434 $time = max( 435 wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ), 436 filemtime( "$IP/resources/src/startup.js" ), 437 $this->getHashMtime( $context ) 438 ); 439 440 // ATTENTION!: Because of the line below, this is not going to cause 441 // infinite recursion - think carefully before making changes to this 442 // code! 443 // Pre-populate modifiedTime with something because the the loop over 444 // all modules below includes the the startup module (this module). 445 $this->modifiedTime[$hash] = 1; 446 447 foreach ( $loader->getModuleNames() as $name ) { 448 $module = $loader->getModule( $name ); 449 $time = max( $time, $module->getModifiedTime( $context ) ); 450 } 451 452 $this->modifiedTime[$hash] = $time; 453 return $this->modifiedTime[$hash]; 454 } 455 456 /** 457 * Hash of all dynamic data embedded in getScript(). 458 * 459 * Detect changes to mw.config settings embedded in #getScript (bug 28899). 460 * 461 * @param ResourceLoaderContext $context 462 * @return string Hash 463 */ 464 public function getModifiedHash( ResourceLoaderContext $context ) { 465 $data = array( 466 'vars' => $this->getConfigSettings( $context ), 467 'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ), 468 ); 469 470 return md5( serialize( $data ) ); 471 } 472 473 /** 474 * @return string 475 */ 476 public function getGroup() { 477 return 'startup'; 478 } 479 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |