* Add a subtitle containing a backlink to a page
*
* @param Title $title Title to link to
+ * @param array $query Array of additional parameters to include in the link
*/
- public function addBacklinkSubtitle( Title $title ) {
- $query = array();
+ public function addBacklinkSubtitle( Title $title, $query = array() ) {
if ( $title->isRedirect() ) {
$query['redirect'] = 'no';
}
);
} else {
$links['html'] .= Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- $resourceLoader->makeModuleResponse( $context, $grpModules )
- )
+ $resourceLoader->makeModuleResponse( $context, $grpModules )
);
}
$links['html'] .= "\n";
/** @var array Associative array mapping module name to info associative array */
protected $moduleInfos = array();
+ /** @var Config $config */
+ private $config;
+
/**
* @var array Associative array mapping framework ids to a list of names of test suite modules
* like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. )
* @return string Filtered data, or a comment containing an error message
*/
public function filter( $filter, $data, $cacheReport = true ) {
- global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
wfProfileIn( __METHOD__ );
// For empty/whitespace-only data or for unknown filters, don't perform
switch ( $filter ) {
case 'minify-js':
$result = JavaScriptMinifier::minify( $data,
- $wgResourceLoaderMinifierStatementsOnOwnLine,
- $wgResourceLoaderMinifierMaxLineLength
+ $this->config->get( 'ResourceLoaderMinifierStatementsOnOwnLine' ),
+ $this->config->get( 'ResourceLoaderMinifierMaxLineLength' )
);
if ( $cacheReport ) {
$result .= "\n/* cache key: $key */";
/**
* Register core modules and runs registration hooks.
*/
- public function __construct() {
- global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript, $wgEnableJavaScriptTest;
+ public function __construct( Config $config = null ) {
+ global $IP;
wfProfileIn( __METHOD__ );
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' was called without providing a Config instance' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+
+ $this->config = $config;
+
// Add 'local' source first
$this->addSource(
'local',
- array( 'loadScript' => $wgLoadScript, 'apiScript' => wfScript( 'api' ) )
+ array( 'loadScript' => wfScript( 'load' ), 'apiScript' => wfScript( 'api' ) )
);
// Add other sources
- $this->addSource( $wgResourceLoaderSources );
+ $this->addSource( $config->get( 'ResourceLoaderSources' ) );
// Register core modules
$this->register( include "$IP/resources/Resources.php" );
// Register extension modules
wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
- $this->register( $wgResourceModules );
+ $this->register( $config->get( 'ResourceModules' ) );
- if ( $wgEnableJavaScriptTest === true ) {
+ if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
$this->registerTestModules();
}
wfProfileOut( __METHOD__ );
}
+ /**
+ * @return Config
+ */
+ public function getConfig() {
+ return $this->config;
+ }
+
/**
* Register a module with the ResourceLoader system.
*
// Apply custom skin-defined styles to existing modules.
if ( $this->isFileModule( $name ) ) {
- global $wgResourceModuleSkinStyles;
- foreach ( $wgResourceModuleSkinStyles as $skinName => $skinStyles ) {
+ foreach ( $this->config->get( 'ResourceModuleSkinStyles' ) as $skinName => $skinStyles ) {
// If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
continue;
/**
*/
public function registerTestModules() {
- global $IP, $wgEnableJavaScriptTest;
+ global $IP;
- if ( $wgEnableJavaScriptTest !== true ) {
+ if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
throw new MWException( 'Attempt to register JavaScript test modules '
. 'but <code>$wgEnableJavaScriptTest</code> is false. '
. 'Edit your <code>LocalSettings.php</code> to enable it.' );
} else {
$class = $info['class'];
}
+ /** @var ResourceLoaderModule $object */
$object = new $class( $info );
+ $object->setConfig( $this->getConfig() );
}
$object->setName( $name );
$this->modules[$name] = $object;
* @param ResourceLoaderContext $context Context in which a response should be formed
*/
public function respond( ResourceLoaderContext $context ) {
- global $wgCacheEpoch, $wgUseFileCache;
-
// Use file cache if enabled and available...
- if ( $wgUseFileCache ) {
+ if ( $this->config->get( 'UseFileCache' ) ) {
$fileCache = ResourceFileCache::newFromContext( $context );
if ( $this->tryRespondFromFileCache( $fileCache, $context ) ) {
return; // output handled
// To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
- $mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch );
+ $mtime = wfTimestamp( TS_UNIX, $this->config->get( 'CacheEpoch' ) );
foreach ( $modules as $module ) {
/**
* @var $module ResourceLoaderModule
* @return void
*/
protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
- global $wgResourceLoaderMaxage;
+ $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
// If a version wasn't specified we need a shorter expiry time for updates
// to propagate to clients quickly
// If there were errors, we also need a shorter expiry time so we can recover quickly
if ( is_null( $context->getVersion() ) || $errors ) {
- $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
- $smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
+ $maxage = $rlMaxage['unversioned']['client'];
+ $smaxage = $rlMaxage['unversioned']['server'];
// If a version was specified we can use a longer expiry time since changing
// version numbers causes cache misses
} else {
- $maxage = $wgResourceLoaderMaxage['versioned']['client'];
- $smaxage = $wgResourceLoaderMaxage['versioned']['server'];
+ $maxage = $rlMaxage['versioned']['client'];
+ $smaxage = $rlMaxage['versioned']['server'];
}
if ( $context->getOnly() === 'styles' ) {
header( 'Content-Type: text/css; charset=utf-8' );
protected function tryRespondFromFileCache(
ResourceFileCache $fileCache, ResourceLoaderContext $context
) {
- global $wgResourceLoaderMaxage;
+ $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
// Buffer output to catch warnings.
ob_start();
// Get the maximum age the cache can be
$maxage = is_null( $context->getVersion() )
- ? $wgResourceLoaderMaxage['unversioned']['server']
- : $wgResourceLoaderMaxage['versioned']['server'];
+ ? $rlMaxage['unversioned']['server']
+ : $rlMaxage['versioned']['server'];
// Minimum timestamp the cache file must have
$good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
if ( !$good ) {
if ( count( $states ) ) {
$out .= self::makeLoaderStateScript( $states );
}
+
+ // In only=script requests for modules that are not raw (e.g. not the startup module)
+ // ensure the execution is conditional to avoid situations where browsers with an
+ // unsupported environment do unconditionally execute a module's scripts. Otherwise users
+ // will get things like "ReferenceError: mw is undefined" or "jQuery is undefined" from
+ // legacy scripts loaded with only=scripts (such as the 'site' module).
+ $out = self::makeLoaderConditionalScript( $out );
} else {
if ( count( $states ) ) {
$exceptions .= self::makeComment(
/**
* Returns LESS compiler set up for use with MediaWiki
*
+ * @param Config $config
+ * @throws MWException
* @since 1.22
* @return lessc
*/
- public static function getLessCompiler() {
- global $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths;
-
+ public static function getLessCompiler( Config $config ) {
// When called from the installer, it is possible that a required PHP extension
// is missing (at least for now; see bug 47564). If this is the case, throw an
// exception (caught by the installer) to prevent a fatal error later on.
$less = new lessc();
$less->setPreserveComments( true );
- $less->setVariables( self::getLESSVars() );
- $less->setImportDir( $wgResourceLoaderLESSImportPaths );
- foreach ( $wgResourceLoaderLESSFunctions as $name => $func ) {
+ $less->setVariables( self::getLESSVars( $config ) );
+ $less->setImportDir( $config->get( 'ResourceLoaderLESSImportPaths' ) );
+ foreach ( $config->get( 'ResourceLoaderLESSFunctions' ) as $name => $func ) {
$less->registerFunction( $name, $func );
}
return $less;
/**
* Get global LESS variables.
*
- * $since 1.22
+ * @param Config $config
+ * @since 1.22
* @return array Map of variable names to string CSS values.
*/
- public static function getLESSVars() {
- global $wgResourceLoaderLESSVars;
-
- $lessVars = $wgResourceLoaderLESSVars;
+ public static function getLESSVars( Config $config ) {
+ $lessVars = $config->get( 'ResourceLoaderLESSVars' );
// Sort by key to ensure consistent hashing for cache lookups.
ksort( $lessVars );
return $lessVars;
* @param ResourceLoaderContext $context
* @return array
*/
- protected function getConfig( $context ) {
+ protected function getConfigSettings( $context ) {
$hash = $context->getHash();
if ( isset( $this->configVars[$hash] ) ) {
return $this->configVars[$hash];
}
- global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
- $wgArticlePath, $wgScriptPath, $wgServer, $wgServerName,
- $wgContLang, $wgVariantArticlePath, $wgActionPaths, $wgVersion,
- $wgEnableAPI, $wgEnableWriteAPI, $wgDBname,
- $wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
- $wgCookiePrefix, $wgCookieDomain, $wgCookiePath,
- $wgCookieExpiration, $wgResourceLoaderMaxQueryLength,
- $wgResourceLoaderStorageEnabled, $wgResourceLoaderStorageVersion,
- $wgSearchType;
+ global $wgContLang;
$mainPage = Title::newMainPage();
}
}
+ $conf = $this->getConfig();
// Build list of variables
$vars = array(
- 'wgLoadScript' => $wgLoadScript,
+ 'wgLoadScript' => wfScript( 'load' ),
'debug' => $context->getDebug(),
'skin' => $context->getSkin(),
- 'stylepath' => $wgStylePath,
+ 'stylepath' => $conf->get( 'StylePath' ),
'wgUrlProtocols' => wfUrlProtocols(),
- 'wgArticlePath' => $wgArticlePath,
- 'wgScriptPath' => $wgScriptPath,
- 'wgScriptExtension' => $wgScriptExtension,
- 'wgScript' => $wgScript,
- 'wgSearchType' => $wgSearchType,
- 'wgVariantArticlePath' => $wgVariantArticlePath,
+ 'wgArticlePath' => $conf->get( 'ArticlePath' ),
+ 'wgScriptPath' => $conf->get( 'ScriptPath' ),
+ 'wgScriptExtension' => $conf->get( 'ScriptExtension' ),
+ 'wgScript' => wfScript(),
+ 'wgSearchType' => $conf->get( 'SearchType' ),
+ 'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ),
// Force object to avoid "empty" associative array from
// becoming [] instead of {} in JS (bug 34604)
- 'wgActionPaths' => (object)$wgActionPaths,
- 'wgServer' => $wgServer,
- 'wgServerName' => $wgServerName,
+ 'wgActionPaths' => (object)$conf->get( 'ActionPaths' ),
+ 'wgServer' => $conf->get( 'Server' ),
+ 'wgServerName' => $conf->get( 'ServerName' ),
'wgUserLanguage' => $context->getLanguage(),
'wgContentLanguage' => $wgContLang->getCode(),
- 'wgVersion' => $wgVersion,
- 'wgEnableAPI' => $wgEnableAPI,
- 'wgEnableWriteAPI' => $wgEnableWriteAPI,
+ 'wgVersion' => $conf->get( 'Version' ),
+ 'wgEnableAPI' => $conf->get( 'EnableAPI' ),
+ 'wgEnableWriteAPI' => $conf->get( 'EnableWriteAPI' ),
'wgMainPageTitle' => $mainPage->getPrefixedText(),
'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
'wgNamespaceIds' => $namespaceIds,
'wgContentNamespaces' => MWNamespace::getContentNamespaces(),
- 'wgSiteName' => $wgSitename,
- 'wgFileExtensions' => array_values( array_unique( $wgFileExtensions ) ),
- 'wgDBname' => $wgDBname,
+ 'wgSiteName' => $conf->get( 'Sitename' ),
+ 'wgFileExtensions' => array_values( array_unique( $conf->get( 'FileExtensions' ) ) ),
+ 'wgDBname' => $conf->get( 'DBname' ),
// This sucks, it is only needed on Special:Upload, but I could
// not find a way to add vars only for a certain module
'wgFileCanRotate' => BitmapHandler::canRotate(),
'wgAvailableSkins' => Skin::getSkinNames(),
- 'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
+ 'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
// MediaWiki sets cookies to have this prefix by default
- 'wgCookiePrefix' => $wgCookiePrefix,
- 'wgCookieDomain' => $wgCookieDomain,
- 'wgCookiePath' => $wgCookiePath,
- 'wgCookieExpiration' => $wgCookieExpiration,
- 'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
+ 'wgCookiePrefix' => $conf->get( 'CookiePrefix' ),
+ 'wgCookieDomain' => $conf->get( 'CookieDomain' ),
+ 'wgCookiePath' => $conf->get( 'CookiePath' ),
+ 'wgCookieExpiration' => $conf->get( 'CookieExpiration' ),
+ 'wgResourceLoaderMaxQueryLength' => $conf->get( 'ResourceLoaderMaxQueryLength' ),
'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
- 'wgResourceLoaderStorageVersion' => $wgResourceLoaderStorageVersion,
- 'wgResourceLoaderStorageEnabled' => $wgResourceLoaderStorageEnabled,
+ 'wgResourceLoaderStorageVersion' => $conf->get( 'ResourceLoaderStorageVersion' ),
+ 'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
);
wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
* @param ResourceLoaderContext $context
* @return string JavaScript code for registering all modules with the client loader
*/
- public static function getModuleRegistrations( ResourceLoaderContext $context ) {
- global $wgCacheEpoch;
+ public function getModuleRegistrations( ResourceLoaderContext $context ) {
wfProfileIn( __METHOD__ );
$resourceLoader = $context->getResourceLoader();
// getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always
// seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
$moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
- $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
+ $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ) );
// FIXME: Convert to numbers, wfTimestamp always gives us stings, even for TS_UNIX
return true;
}
+ /**
+ * Base modules required for the the base environment of ResourceLoader
+ *
+ * @return array
+ */
+ public static function getStartupModules() {
+ return array( 'jquery', 'mediawiki' );
+ }
+
/**
* Get the load URL of the startup modules.
*
* @return string
*/
public static function getStartupModulesUrl( ResourceLoaderContext $context ) {
- // The core modules:
- $moduleNames = array( 'jquery', 'mediawiki' );
+ $moduleNames = self::getStartupModules();
// Get the latest version
$loader = $context->getResourceLoader();
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
- global $IP, $wgLegacyJavaScriptGlobals;
+ global $IP;
$out = file_get_contents( "$IP/resources/src/startup.js" );
if ( $context->getOnly() === 'scripts' ) {
// Startup function
- $configuration = $this->getConfig( $context );
- $registrations = self::getModuleRegistrations( $context );
+ $configuration = $this->getConfigSettings( $context );
+ $registrations = $this->getModuleRegistrations( $context );
// Fix indentation
$registrations = str_replace( "\n", "\n\t", trim( $registrations ) );
$out .= "var startUp = function () {\n" .
"\tmw.config = new " .
- Xml::encodeJsCall( 'mw.Map', array( $wgLegacyJavaScriptGlobals ) ) . "\n" .
+ Xml::encodeJsCall( 'mw.Map', array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" .
"\t$registrations\n" .
"\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
"};\n";
* @return array|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- global $IP, $wgCacheEpoch;
+ global $IP;
$hash = $context->getHash();
if ( isset( $this->modifiedTime[$hash] ) ) {
$loader->preloadModuleInfo( $loader->getModuleNames(), $context );
$time = max(
- wfTimestamp( TS_UNIX, $wgCacheEpoch ),
+ wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ),
filemtime( "$IP/resources/src/startup.js" ),
$this->getHashMtime( $context )
);
* @return string Hash
*/
public function getModifiedHash( ResourceLoaderContext $context ) {
- global $wgLegacyJavaScriptGlobals;
-
$data = array(
- 'vars' => $this->getConfig( $context ),
- 'wgLegacyJavaScriptGlobals' => $wgLegacyJavaScriptGlobals,
+ 'vars' => $this->getConfigSettings( $context ),
+ 'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
);
return md5( serialize( $data ) );
array(
'resources/lib/jquery/jquery.js'
) ),
- 'debugRaw' => false,
+ 'raw' => true,
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki' => array(
'scripts' => 'resources/src/mediawiki/mediawiki.js',
'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',
- 'debugRaw' => false,
+ 'raw' => true,
'targets' => array( 'desktop', 'mobile' ),
),
'mediawiki.api' => array(
'user.tokens',
),
),
+ 'mediawiki.content.json' => array(
+ 'styles' => 'resources/src/mediawiki/mediawiki.content.json.css',
+ ),
'mediawiki.debug' => array(
'scripts' => array(
'resources/src/mediawiki/mediawiki.debug.js',
* options['expectedReturn'] - expected return value
* options['message'] - PHPUnit message for assertion
*
- * @param array $args key-value array of arguments as shown above
+ * @param array $args Key-value array of arguments as shown above
*/
protected function assertTransformCssMediaCase( $args ) {
$queryData = array();
// Load module script only
array(
array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ),
- '<script src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=vector&*"></script>
+ '<script src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=fallback&*"></script>
'
),
// Load module styles only
// This also tests the order the modules are put into the url
array(
array( array( 'test.baz', 'test.foo', 'test.bar' ), ResourceLoaderModule::TYPE_STYLES ),
- '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=vector&*">
+ '<link rel=stylesheet href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback&*">
'
),
// Load private module (combined)
array(
array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ),
- '<script>if(window.mw){
- mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n/* cache key: wiki:resourceloader:filter:minify-css:7:fd8ea20b3336b2bfb230c789d430067a */"]},{});
- /* cache key: wiki:resourceloader:filter:minify-js:7:274ccee17be73cd5f4dda5dc2a819188 */
- }</script>
+ '<script>if(window.mw){mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]},{});}
+ </script>
'
),
// Load module script with with ESI
array(
array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ),
- '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=vector&*" /></script>
+ '<script><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=scripts&skin=fallback&*" /></script>
'
),
// Load module styles with with ESI
array(
array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ),
- '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=styles&skin=vector&*" /></style>
+ '<style><esi:include src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.foo&only=styles&skin=fallback&*" /></style>
',
),
);
'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php',
// Affects whether CDATA is inserted
'wgWellFormedXml' => false,
- // Cache key is based on database name, and affects output;
- // this test should not touch the database anyways.
- 'wgDBname' => 'wiki',
- 'wgDBprefix' => '',
) );
$class = new ReflectionClass( 'OutputPage' );
$method = $class->getMethod( 'makeResourceLoaderLink' );
$method->setAccessible( true );
$ctx = new RequestContext();
- $ctx->setSkin( Skin::newFromKey( 'vector' ) );
+ $ctx->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'fallback' ) );
$ctx->setLanguage( 'en' );
$out = new OutputPage( $ctx );
$rl = $out->getResourceLoader();
)),
) );
$links = $method->invokeArgs( $out, $args );
- $this->assertEquals( $expectedHtml, $links['html'] );
+ // Strip comments to avoid variation due to wgDBname in WikiID and cache key
+ $actualHtml = preg_replace( '#/\*[^*]+\*/#', '', $links['html'] );
+ $this->assertEquals( $expectedHtml, $actualHtml );
}
}