[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/resourceloader/ -> ResourceLoaderStartUpModule.php (source)

   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  }
". ', [['includes','Html.php',597]], 7], 'wfappendquery': ['wfappendquery', 'Append a query string to an existing URL, which may or may not already have query string parameters already. If so, they will be combined. ', [['includes','GlobalFunctions.php',471]], 30], 'getmoduleregistrations': ['getmoduleregistrations', 'Get registration code for all modules. ', [['includes/resourceloader','ResourceLoaderStartUpModule.php',187]], 1], 'getmodulenames': ['getmodulenames', 'Get a list of module names. ', [['includes/resourceloader','ResourceLoader.php',434]], 5], 'makeloadersourcesscript': ['makeloadersourcesscript', 'Returns JS code which calls mw.loader.addSource() with the given parameters. Has two calling conventions: ', [['includes/resourceloader','ResourceLoader.php',1213]], 1], 'getconfig': ['getconfig', 'Get the Config object ', [['includes/context','ContextSource.php',62],['includes/specialpage','SpecialPage.php',547],['includes/context','DerivativeContext.php',90],['maintenance','Maintenance.php',471],['includes/resourceloader','ResourceLoaderModule.php',141],['includes/resourceloader','ResourceLoader.php',252],['includes/context','RequestContext.php',85],['includes','SiteConfiguration.php',505],['includes/context','IContextSource.php',94]], 309], 'gethashmtime': ['gethashmtime', 'Helper method for calculating when the module\'s hash (if it has one) changed. ', [['includes/resourceloader','ResourceLoaderModule.php',440]], 4], 'getprefixedtext': ['getprefixedtext', 'Returns the title formatted for display, including the namespace name. ', [['includes/title','TitleFormatter.php',59],['includes','Title.php',1416],['includes/title','MediaWikiTitleCodec.php',168]], 336], 'filemtime': ['filemtime', '', [], 13], 'array_values': ['array_values', '', [], 86], 'max': ['max', '', [], 197], 'serialize': ['serialize', '', [], 118], 'ksort': ['ksort', '', [], 33], 'array_merge': ['array_merge', '', [], 382], 'count': ['count', '', [], 1475], 'array_diff': ['array_diff', '', [], 70], 'trim': ['trim', '', [], 340], 'array_unique': ['array_unique', '', [], 102], 'file_get_contents': ['file_get_contents', '', [], 91], 'in_array': ['in_array', '', [], 760], 'str_replace': ['str_replace', '', [], 391]}; CLASS_DATA={ 'xml': ['xml', 'Module of static functions for generating XML ', [['includes','Xml.php',23]], 911], 'resourceloaderstartupmodule': ['resourceloaderstartupmodule', 'Module for resource loader initialization. ', [['includes/resourceloader','ResourceLoaderStartUpModule.php',2]], 1], 'mwnamespace': ['mwnamespace', 'This is a utility class with only static functions for dealing with namespaces that encodes all the "magic" behaviors of them based on index. The textual names of the namespaces are handled by Language.php. ', [['includes','MWNamespace.php',23]], 132], 'resourceloader': ['resourceloader', 'Dynamic JavaScript and CSS resource loading system. ', [['includes/resourceloader','ResourceLoader.php',25]], 48], 'title': ['title', 'Represents a title within MediaWiki. Optionally may contain an interwiki designation or namespace. ', [['includes','Title.php',25]], 710], 'html': ['html', 'This class is a collection of static functions that serve two purposes: ', [['includes','Html.php',26]], 764], 'specialupload': ['specialupload', 'Form for handling uploads and special page. ', [['includes/specials','SpecialUpload.php',25]], 4]}; CONST_DATA={ 'TS_ISO_8601_BASIC': ['TS_ISO_8601_BASIC', '', [['includes','GlobalFunctions.php',2430]], 6], 'TS_UNIX': ['TS_UNIX', '', [['includes','GlobalFunctions.php',2384]], 74]};


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1