summaryrefslogtreecommitdiff
path: root/includes/resourceloader
diff options
context:
space:
mode:
Diffstat (limited to 'includes/resourceloader')
-rw-r--r--includes/resourceloader/DerivativeResourceLoaderContext.php202
-rw-r--r--includes/resourceloader/ResourceLoader.php670
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php49
-rw-r--r--includes/resourceloader/ResourceLoaderEditToolbarModule.php102
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php473
-rw-r--r--includes/resourceloader/ResourceLoaderFilePageModule.php2
-rw-r--r--includes/resourceloader/ResourceLoaderFilePath.php74
-rw-r--r--includes/resourceloader/ResourceLoaderLESSFunctions.php67
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageDataModule.php84
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageNamesModule.php79
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php221
-rw-r--r--includes/resourceloader/ResourceLoaderNoscriptModule.php6
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php12
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php412
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php31
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php21
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php18
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php16
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php10
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php110
20 files changed, 1879 insertions, 780 deletions
diff --git a/includes/resourceloader/DerivativeResourceLoaderContext.php b/includes/resourceloader/DerivativeResourceLoaderContext.php
new file mode 100644
index 00000000..d114d7ed
--- /dev/null
+++ b/includes/resourceloader/DerivativeResourceLoaderContext.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Derivative context for resource loader modules.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Kunal Mehta
+ */
+
+/**
+ * Allows changing specific properties of a context object,
+ * without changing the main one. Inspired by DerivativeContext.
+ *
+ * @since 1.24
+ */
+class DerivativeResourceLoaderContext extends ResourceLoaderContext {
+
+ /**
+ * @var ResourceLoaderContext
+ */
+ private $context;
+ protected $modules;
+ protected $language;
+ protected $direction;
+ protected $skin;
+ protected $user;
+ protected $debug;
+ protected $only;
+ protected $version;
+ protected $hash;
+ protected $raw;
+
+ public function __construct( ResourceLoaderContext $context ) {
+ $this->context = $context;
+ }
+
+ public function getModules() {
+ if ( !is_null( $this->modules ) ) {
+ return $this->modules;
+ } else {
+ return $this->context->getModules();
+ }
+ }
+
+ /**
+ * @param string[] $modules
+ */
+ public function setModules( array $modules ) {
+ $this->modules = $modules;
+ }
+
+ public function getLanguage() {
+ if ( !is_null( $this->language ) ) {
+ return $this->language;
+ } else {
+ return $this->context->getLanguage();
+ }
+ }
+
+ /**
+ * @param string $language
+ */
+ public function setLanguage( $language ) {
+ $this->language = $language;
+ $this->direction = null; // Invalidate direction since it might be based on language
+ $this->hash = null;
+ }
+
+ public function getDirection() {
+ if ( !is_null( $this->direction ) ) {
+ return $this->direction;
+ } else {
+ return $this->context->getDirection();
+ }
+ }
+
+ /**
+ * @param string $direction
+ */
+ public function setDirection( $direction ) {
+ $this->direction = $direction;
+ $this->hash = null;
+ }
+
+ public function getSkin() {
+ if ( !is_null( $this->skin ) ) {
+ return $this->skin;
+ } else {
+ return $this->context->getSkin();
+ }
+ }
+
+ /**
+ * @param string $skin
+ */
+ public function setSkin( $skin ) {
+ $this->skin = $skin;
+ $this->hash = null;
+ }
+
+ public function getUser() {
+ if ( !is_null( $this->user ) ) {
+ return $this->user;
+ } else {
+ return $this->context->getUser();
+ }
+ }
+
+ /**
+ * @param string $user
+ */
+ public function setUser( $user ) {
+ $this->user = $user;
+ $this->hash = null;
+ }
+
+ public function getDebug() {
+ if ( !is_null( $this->debug ) ) {
+ return $this->debug;
+ } else {
+ return $this->context->getDebug();
+ }
+ }
+
+ /**
+ * @param bool $debug
+ */
+ public function setDebug( $debug ) {
+ $this->debug = $debug;
+ $this->hash = null;
+ }
+
+ public function getOnly() {
+ if ( !is_null( $this->only ) ) {
+ return $this->only;
+ } else {
+ return $this->context->getOnly();
+ }
+ }
+
+ /**
+ * @param string $only
+ */
+ public function setOnly( $only ) {
+ $this->only = $only;
+ $this->hash = null;
+ }
+
+ public function getVersion() {
+ if ( !is_null( $this->version ) ) {
+ return $this->version;
+ } else {
+ return $this->context->getVersion();
+ }
+ }
+
+ /**
+ * @param string $version
+ */
+ public function setVersion( $version ) {
+ $this->version = $version;
+ $this->hash = null;
+ }
+
+ public function getRaw() {
+ if ( !is_null( $this->raw ) ) {
+ return $this->raw;
+ } else {
+ return $this->context->getRaw();
+ }
+ }
+
+ /**
+ * @param bool $raw
+ */
+ public function setRaw( $raw ) {
+ $this->raw = $raw;
+ }
+
+ public function getRequest() {
+ return $this->context->getRequest();
+ }
+
+ public function getResourceLoader() {
+ return $this->context->getResourceLoader();
+ }
+
+}
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 6380efcf..4f1414bc 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -25,35 +25,39 @@
/**
* Dynamic JavaScript and CSS resource loading system.
*
- * Most of the documention is on the MediaWiki documentation wiki starting at:
- * http://www.mediawiki.org/wiki/ResourceLoader
+ * Most of the documentation is on the MediaWiki documentation wiki starting at:
+ * https://www.mediawiki.org/wiki/ResourceLoader
*/
class ResourceLoader {
-
- /* Protected Static Members */
+ /** @var int */
protected static $filterCacheVersion = 7;
- protected static $requiredSourceProperties = array( 'loadScript' );
- /** Array: List of module name/ResourceLoaderModule object pairs */
+ /** @var bool */
+ protected static $debugMode = null;
+
+ /** @var array Module name/ResourceLoaderModule object pairs */
protected $modules = array();
- /** Associative array mapping module name to info associative array */
+ /** @var array Associative array mapping module name to info associative array */
protected $moduleInfos = 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', .. ), .. ) */
+ /** @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', .. ), .. )
+ */
protected $testModuleNames = array();
- /** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/
+ /** @var array E.g. array( 'source-id' => 'http://.../load.php' ) */
protected $sources = array();
/** @var bool */
protected $hasErrors = false;
- /* Protected Methods */
-
/**
- * Loads information stored in the database about modules.
+ * Load information stored in the database about modules.
*
* This method grabs modules dependencies from the database and updates modules
* objects.
@@ -64,11 +68,12 @@ class ResourceLoader {
* performance improvement.
*
* @param array $modules List of module names to preload information for
- * @param $context ResourceLoaderContext: Context to load the information within
+ * @param ResourceLoaderContext $context Context to load the information within
*/
public function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) {
if ( !count( $modules ) ) {
- return; // or else Database*::select() will explode, plus it's cheaper!
+ // Or else Database*::select() will explode, plus it's cheaper!
+ return;
}
$dbr = wfGetDB( DB_SLAVE );
$skin = $context->getSkin();
@@ -84,21 +89,26 @@ class ResourceLoader {
// Set modules' dependencies
$modulesWithDeps = array();
foreach ( $res as $row ) {
- $this->getModule( $row->md_module )->setFileDependencies( $skin,
- FormatJson::decode( $row->md_deps, true )
- );
- $modulesWithDeps[] = $row->md_module;
+ $module = $this->getModule( $row->md_module );
+ if ( $module ) {
+ $module->setFileDependencies( $skin, FormatJson::decode( $row->md_deps, true ) );
+ $modulesWithDeps[] = $row->md_module;
+ }
}
// Register the absence of a dependency row too
foreach ( array_diff( $modules, $modulesWithDeps ) as $name ) {
- $this->getModule( $name )->setFileDependencies( $skin, array() );
+ $module = $this->getModule( $name );
+ if ( $module ) {
+ $this->getModule( $name )->setFileDependencies( $skin, array() );
+ }
}
// Get message blob mtimes. Only do this for modules with messages
$modulesWithMessages = array();
foreach ( $modules as $name ) {
- if ( count( $this->getModule( $name )->getMessages() ) ) {
+ $module = $this->getModule( $name );
+ if ( $module && count( $module->getMessages() ) ) {
$modulesWithMessages[] = $name;
}
}
@@ -110,39 +120,43 @@ class ResourceLoader {
), __METHOD__
);
foreach ( $res as $row ) {
- $this->getModule( $row->mr_resource )->setMsgBlobMtime( $lang,
- wfTimestamp( TS_UNIX, $row->mr_timestamp ) );
- unset( $modulesWithoutMessages[$row->mr_resource] );
+ $module = $this->getModule( $row->mr_resource );
+ if ( $module ) {
+ $module->setMsgBlobMtime( $lang, wfTimestamp( TS_UNIX, $row->mr_timestamp ) );
+ unset( $modulesWithoutMessages[$row->mr_resource] );
+ }
}
}
foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
- $this->getModule( $name )->setMsgBlobMtime( $lang, 0 );
+ $module = $this->getModule( $name );
+ if ( $module ) {
+ $module->setMsgBlobMtime( $lang, 0 );
+ }
}
}
/**
- * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls.
+ * Run JavaScript or CSS data through a filter, caching the filtered result for future calls.
*
* Available filters are:
- * - minify-js \see JavaScriptMinifier::minify
- * - minify-css \see CSSMin::minify
+ *
+ * - minify-js \see JavaScriptMinifier::minify
+ * - minify-css \see CSSMin::minify
*
* If $data is empty, only contains whitespace or the filter was unknown,
* $data is returned unmodified.
*
* @param string $filter Name of filter to run
* @param string $data Text to filter, such as JavaScript or CSS text
- * @return String: Filtered data, or a comment containing an error message
+ * @param string $cacheReport Whether to include the cache key report
+ * @return string Filtered data, or a comment containing an error message
*/
- protected function filter( $filter, $data ) {
- global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
+ public function filter( $filter, $data, $cacheReport = true ) {
wfProfileIn( __METHOD__ );
// For empty/whitespace-only data or for unknown filters, don't perform
// any caching or processing
- if ( trim( $data ) === ''
- || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) )
- {
+ if ( trim( $data ) === '' || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) ) {
wfProfileOut( __METHOD__ );
return $data;
}
@@ -165,14 +179,18 @@ class ResourceLoader {
switch ( $filter ) {
case 'minify-js':
$result = JavaScriptMinifier::minify( $data,
- $wgResourceLoaderMinifierStatementsOnOwnLine,
- $wgResourceLoaderMinifierMaxLineLength
+ $this->config->get( 'ResourceLoaderMinifierStatementsOnOwnLine' ),
+ $this->config->get( 'ResourceLoaderMinifierMaxLineLength' )
);
- $result .= "\n/* cache key: $key */";
+ if ( $cacheReport ) {
+ $result .= "\n/* cache key: $key */";
+ }
break;
case 'minify-css':
$result = CSSMin::minify( $data );
- $result .= "\n/* cache key: $key */";
+ if ( $cacheReport ) {
+ $result .= "\n/* cache key: $key */";
+ }
break;
}
@@ -194,26 +212,34 @@ class ResourceLoader {
/* Methods */
/**
- * Registers core modules and runs registration hooks.
+ * Register core modules and runs registration hooks.
+ * @param Config|null $config
*/
- 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' ) ) );
+ $this->addSource( 'local', wfScript( 'load' ) );
// 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();
}
@@ -221,17 +247,24 @@ class ResourceLoader {
}
/**
- * Registers a module with the ResourceLoader system.
+ * @return Config
+ */
+ public function getConfig() {
+ return $this->config;
+ }
+
+ /**
+ * Register a module with the ResourceLoader system.
*
- * @param $name Mixed: Name of module as a string or List of name/object pairs as an array
+ * @param mixed $name Name of module as a string or List of name/object pairs as an array
* @param array $info Module info array. For backwards compatibility with 1.17alpha,
* this may also be a ResourceLoaderModule object. Optional when using
* multiple-registration calling style.
- * @throws MWException: If a duplicate module registration is attempted
- * @throws MWException: If a module name contains illegal characters (pipes or commas)
- * @throws MWException: If something other than a ResourceLoaderModule is being registered
- * @return Boolean: False if there were any errors, in which case one or more modules were not
- * registered
+ * @throws MWException If a duplicate module registration is attempted
+ * @throws MWException If a module name contains illegal characters (pipes or commas)
+ * @throws MWException If something other than a ResourceLoaderModule is being registered
+ * @return bool False if there were any errors, in which case one or more modules were
+ * not registered
*/
public function register( $name, $info = null ) {
wfProfileIn( __METHOD__ );
@@ -252,25 +285,64 @@ class ResourceLoader {
// Check $name for validity
if ( !self::isValidModuleName( $name ) ) {
wfProfileOut( __METHOD__ );
- throw new MWException( "ResourceLoader module name '$name' is invalid, see ResourceLoader::isValidModuleName()" );
+ throw new MWException( "ResourceLoader module name '$name' is invalid, "
+ . "see ResourceLoader::isValidModuleName()" );
}
// Attach module
- if ( is_object( $info ) ) {
- // Old calling convention
- // Validate the input
- if ( !( $info instanceof ResourceLoaderModule ) ) {
- wfProfileOut( __METHOD__ );
- throw new MWException( 'ResourceLoader invalid module error. ' .
- 'Instances of ResourceLoaderModule expected.' );
- }
-
+ if ( $info instanceof ResourceLoaderModule ) {
$this->moduleInfos[$name] = array( 'object' => $info );
$info->setName( $name );
$this->modules[$name] = $info;
- } else {
+ } elseif ( is_array( $info ) ) {
// New calling convention
$this->moduleInfos[$name] = $info;
+ } else {
+ wfProfileOut( __METHOD__ );
+ throw new MWException(
+ 'ResourceLoader module info type error for module \'' . $name .
+ '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
+ );
+ }
+
+ // Last-minute changes
+
+ // Apply custom skin-defined styles to existing modules.
+ if ( $this->isFileModule( $name ) ) {
+ 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;
+ }
+
+ // If $name is preceded with a '+', the defined style files will be added to 'default'
+ // skinStyles, otherwise 'default' will be ignored as it normally would be.
+ if ( isset( $skinStyles[$name] ) ) {
+ $paths = (array)$skinStyles[$name];
+ $styleFiles = array();
+ } elseif ( isset( $skinStyles['+' . $name] ) ) {
+ $paths = (array)$skinStyles['+' . $name];
+ $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
+ $this->moduleInfos[$name]['skinStyles']['default'] :
+ array();
+ } else {
+ continue;
+ }
+
+ // Add new file paths, remapping them to refer to our directories and not use settings
+ // from the module we're modifying. These can come from the base definition or be defined
+ // for each module.
+ list( $localBasePath, $remoteBasePath ) =
+ ResourceLoaderFileModule::extractBasePaths( $skinStyles );
+ list( $localBasePath, $remoteBasePath ) =
+ ResourceLoaderFileModule::extractBasePaths( $paths, $localBasePath, $remoteBasePath );
+
+ foreach ( $paths as $path ) {
+ $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
+ }
+
+ $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
+ }
}
}
@@ -280,17 +352,19 @@ class ResourceLoader {
/**
*/
public function registerTestModules() {
- global $IP, $wgEnableJavaScriptTest;
+ global $IP;
- if ( $wgEnableJavaScriptTest !== 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.' );
+ 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.' );
}
wfProfileIn( __METHOD__ );
// Get core test suites
$testModules = array();
- $testModules['qunit'] = include "$IP/tests/qunit/QUnitTestResources.php";
+ $testModules['qunit'] = array();
// Get other test suites (e.g. from extensions)
wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
@@ -301,9 +375,12 @@ class ResourceLoader {
// on document-ready, it will run once and finish. If some tests arrive
// later (possibly after QUnit has already finished) they will be ignored.
$module['position'] = 'top';
- $module['dependencies'][] = 'mediawiki.tests.qunit.testrunner';
+ $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
}
+ $testModules['qunit'] =
+ ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
+
foreach ( $testModules as $id => $names ) {
// Register test modules
$this->register( $testModules[$id] );
@@ -318,14 +395,12 @@ class ResourceLoader {
/**
* Add a foreign source of modules.
*
- * Source properties:
- * 'loadScript': URL (either fully-qualified or protocol-relative) of load.php for this source
- *
- * @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
- * @param array $properties source properties
+ * @param array|string $id Source ID (string), or array( id1 => loadUrl, id2 => loadUrl, ... )
+ * @param string|array $loadUrl load.php url (string), or array with loadUrl key for
+ * backwards-compatibility.
* @throws MWException
*/
- public function addSource( $id, $properties = null ) {
+ public function addSource( $id, $loadUrl = null ) {
// Allow multiple sources to be registered in one call
if ( is_array( $id ) ) {
foreach ( $id as $key => $value ) {
@@ -342,20 +417,24 @@ class ResourceLoader {
);
}
- // Validate properties
- foreach ( self::$requiredSourceProperties as $prop ) {
- if ( !isset( $properties[$prop] ) ) {
- throw new MWException( "Required property $prop missing from source ID $id" );
+ // Pre 1.24 backwards-compatibility
+ if ( is_array( $loadUrl ) ) {
+ if ( !isset( $loadUrl['loadScript'] ) ) {
+ throw new MWException(
+ __METHOD__ . ' was passed an array with no "loadScript" key.'
+ );
}
+
+ $loadUrl = $loadUrl['loadScript'];
}
- $this->sources[$id] = $properties;
+ $this->sources[$id] = $loadUrl;
}
/**
- * Get a list of module names
+ * Get a list of module names.
*
- * @return Array: List of module names
+ * @return array List of module names
*/
public function getModuleNames() {
return array_keys( $this->moduleInfos );
@@ -363,18 +442,21 @@ class ResourceLoader {
/**
* Get a list of test module names for one (or all) frameworks.
+ *
* If the given framework id is unknkown, or if the in-object variable is not an array,
* then it will return an empty array.
*
- * @param string $framework Optional. Get only the test module names for one
- * particular framework.
- * @return Array
+ * @param string $framework Get only the test module names for one
+ * particular framework (optional)
+ * @return array
*/
public function getTestModuleNames( $framework = 'all' ) {
- /// @todo api siteinfo prop testmodulenames modulenames
+ /** @todo api siteinfo prop testmodulenames modulenames */
if ( $framework == 'all' ) {
return $this->testModuleNames;
- } elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) {
+ } elseif ( isset( $this->testModuleNames[$framework] )
+ && is_array( $this->testModuleNames[$framework] )
+ ) {
return $this->testModuleNames[$framework];
} else {
return array();
@@ -384,8 +466,13 @@ class ResourceLoader {
/**
* Get the ResourceLoaderModule object for a given module name.
*
+ * If an array of module parameters exists but a ResourceLoaderModule object has not
+ * yet been instantiated, this method will instantiate and cache that object such that
+ * subsequent calls simply return the same object.
+ *
* @param string $name Module name
- * @return ResourceLoaderModule if module has been registered, null otherwise
+ * @return ResourceLoaderModule|null If module has been registered, return a
+ * ResourceLoaderModule instance. Otherwise, return null.
*/
public function getModule( $name ) {
if ( !isset( $this->modules[$name] ) ) {
@@ -405,7 +492,9 @@ class ResourceLoader {
} else {
$class = $info['class'];
}
+ /** @var ResourceLoaderModule $object */
$object = new $class( $info );
+ $object->setConfig( $this->getConfig() );
}
$object->setName( $name );
$this->modules[$name] = $object;
@@ -415,24 +504,55 @@ class ResourceLoader {
}
/**
- * Get the list of sources
+ * Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule.
*
- * @return Array: array( id => array of properties, .. )
+ * @param string $name Module name
+ * @return bool
+ */
+ protected function isFileModule( $name ) {
+ if ( !isset( $this->moduleInfos[$name] ) ) {
+ return false;
+ }
+ $info = $this->moduleInfos[$name];
+ if ( isset( $info['object'] ) || isset( $info['class'] ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the list of sources.
+ *
+ * @return array Like array( id => load.php url, .. )
*/
public function getSources() {
return $this->sources;
}
/**
- * Outputs a response to a resource load-request, including a content-type header.
+ * Get the URL to the load.php endpoint for the given
+ * ResourceLoader source
*
- * @param $context ResourceLoaderContext: Context in which a response should be formed
+ * @since 1.24
+ * @param string $source
+ * @throws MWException On an invalid $source name
+ * @return string
*/
- public function respond( ResourceLoaderContext $context ) {
- global $wgCacheEpoch, $wgUseFileCache;
+ public function getLoadScript( $source ) {
+ if ( !isset( $this->sources[$source] ) ) {
+ throw new MWException( "The $source source was never registered in ResourceLoader." );
+ }
+ return $this->sources[$source];
+ }
+ /**
+ * Output a response to a load request, including the content-type header.
+ *
+ * @param ResourceLoaderContext $context Context in which a response should be formed
+ */
+ public function respond( ResourceLoaderContext $context ) {
// 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
@@ -451,12 +571,12 @@ class ResourceLoader {
wfProfileIn( __METHOD__ );
$errors = '';
- // Split requested modules into two groups, modules and missing
+ // Find out which modules are missing and instantiate the others
$modules = array();
$missing = array();
foreach ( $context->getModules() as $name ) {
- if ( isset( $this->moduleInfos[$name] ) ) {
- $module = $this->getModule( $name );
+ $module = $this->getModule( $name );
+ if ( $module ) {
// Do not allow private modules to be loaded from the web.
// This is a security issue, see bug 34907.
if ( $module->getGroup() === 'private' ) {
@@ -488,7 +608,7 @@ class ResourceLoader {
// 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
@@ -527,7 +647,7 @@ class ResourceLoader {
}
// Save response to file cache unless there are errors
- if ( isset( $fileCache ) && !$errors && !$missing ) {
+ if ( isset( $fileCache ) && !$errors && !count( $missing ) ) {
// Cache single modules...and other requests if there are enough hits
if ( ResourceFileCache::useFileCache( $context ) ) {
if ( $fileCache->isCacheWorthy() ) {
@@ -550,24 +670,24 @@ class ResourceLoader {
/**
* Send content type and last modified headers to the client.
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @param string $mtime TS_MW timestamp to use for last-modified
* @param bool $errors Whether there are commented-out errors in the response
* @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' );
@@ -588,9 +708,12 @@ class ResourceLoader {
}
/**
+ * Respond with 304 Last Modified if appropiate.
+ *
* If there's an If-Modified-Since header, respond with a 304 appropriately
* and clear out the output buffer. If the client cache is too old then do nothing.
- * @param $context ResourceLoaderContext
+ *
+ * @param ResourceLoaderContext $context
* @param string $mtime The TS_MW timestamp to check the header against
* @return bool True if 304 header sent and output handled
*/
@@ -623,22 +746,22 @@ class ResourceLoader {
}
/**
- * Send out code for a response from file cache if possible
+ * Send out code for a response from file cache if possible.
*
- * @param $fileCache ResourceFileCache: Cache object for this request URL
- * @param $context ResourceLoaderContext: Context in which to generate a response
+ * @param ResourceFileCache $fileCache Cache object for this request URL
+ * @param ResourceLoaderContext $context Context in which to generate a response
* @return bool If this found a cache file and handled the response
*/
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 ) {
@@ -674,10 +797,11 @@ class ResourceLoader {
}
/**
- * Generate a CSS or JS comment block. Only use this for public data,
- * not error message details.
+ * Generate a CSS or JS comment block.
*
- * @param $text string
+ * Only use this for public data, not error message details.
+ *
+ * @param string $text
* @return string
*/
public static function makeComment( $text ) {
@@ -686,10 +810,10 @@ class ResourceLoader {
}
/**
- * Handle exception display
+ * Handle exception display.
*
- * @param Exception $e to be shown to the user
- * @return string sanitized text that can be returned to the user
+ * @param Exception $e Exception to be shown to the user
+ * @return string Sanitized text that can be returned to the user
*/
public static function formatException( $e ) {
global $wgShowExceptionDetails;
@@ -702,30 +826,38 @@ class ResourceLoader {
}
/**
- * Generates code for a response
+ * Generate code for a response.
*
- * @param $context ResourceLoaderContext: Context in which to generate a response
+ * @param ResourceLoaderContext $context Context in which to generate a response
* @param array $modules List of module objects keyed by module name
- * @param array $missing List of unavailable modules (optional)
- * @return String: Response data
+ * @param array $missing List of requested module names that are unregistered (optional)
+ * @return string Response data
*/
public function makeModuleResponse( ResourceLoaderContext $context,
- array $modules, $missing = array()
+ array $modules, array $missing = array()
) {
$out = '';
$exceptions = '';
- if ( $modules === array() && $missing === array() ) {
- return '/* No modules requested. Max made me put this here */';
+ $states = array();
+
+ if ( !count( $modules ) && !count( $missing ) ) {
+ return "/* This file is the Web entry point for MediaWiki's ResourceLoader:
+ <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
+ no modules were requested. Max made me put this here. */";
}
wfProfileIn( __METHOD__ );
+
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
try {
- $blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() );
+ $blobs = MessageBlobStore::getInstance()->get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
- wfDebugLog( 'resourceloader', __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e" );
+ wfDebugLog(
+ 'resourceloader',
+ __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e"
+ );
$this->hasErrors = true;
// Add exception to the output as a comment
$exceptions .= self::formatException( $e );
@@ -734,6 +866,10 @@ class ResourceLoader {
$blobs = array();
}
+ foreach ( $missing as $name ) {
+ $states[$name] = 'missing';
+ }
+
// Generate output
$isRaw = false;
foreach ( $modules as $name => $module ) {
@@ -753,9 +889,15 @@ class ResourceLoader {
$scripts = $module->getScriptURLsForDebug( $context );
} else {
$scripts = $module->getScript( $context );
- if ( is_string( $scripts ) && strlen( $scripts ) && substr( $scripts, -1 ) !== ';' ) {
- // bug 27054: Append semicolon to prevent weird bugs
- // caused by files not terminating their statements right
+ // rtrim() because there are usually a few line breaks
+ // after the last ';'. A new line at EOF, a new line
+ // added by ResourceLoaderFileModule::readScriptFiles, etc.
+ if ( is_string( $scripts )
+ && strlen( $scripts )
+ && substr( rtrim( $scripts ), -1 ) !== ';'
+ ) {
+ // Append semicolon to prevent weird bugs caused by files not
+ // terminating their statements right (bug 27054)
$scripts .= ";\n";
}
}
@@ -766,7 +908,7 @@ class ResourceLoader {
// Don't create empty stylesheets like array( '' => '' ) for modules
// that don't *have* any stylesheets (bug 38024).
$stylePairs = $module->getStyles( $context );
- if ( count ( $stylePairs ) ) {
+ if ( count( $stylePairs ) ) {
// If we are in debug mode without &only= set, we'll want to return an array of URLs
// See comment near shouldIncludeScripts() for more details
if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
@@ -838,8 +980,8 @@ class ResourceLoader {
// Add exception to the output as a comment
$exceptions .= self::formatException( $e );
- // Register module as missing
- $missing[] = $name;
+ // Respond to client with error-state instead of module implementation
+ $states[$name] = 'error';
unset( $modules[$name] );
}
$isRaw |= $module->isRaw();
@@ -848,14 +990,23 @@ class ResourceLoader {
// Update module states
if ( $context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw ) {
- // Set the state of modules loaded as only scripts to ready
if ( count( $modules ) && $context->getOnly() === 'scripts' ) {
- $out .= self::makeLoaderStateScript(
- array_fill_keys( array_keys( $modules ), 'ready' ) );
+ // Set the state of modules loaded as only scripts to ready as
+ // they don't have an mw.loader.implement wrapper that sets the state
+ foreach ( $modules as $name => $module ) {
+ $states[$name] = 'ready';
+ }
+ }
+
+ // Set the state of modules we didn't respond to with mw.loader.implement
+ if ( count( $states ) ) {
+ $out .= self::makeLoaderStateScript( $states );
}
- // Set the state of modules which were requested but unavailable as missing
- if ( is_array( $missing ) && count( $missing ) ) {
- $out .= self::makeLoaderStateScript( array_fill_keys( $missing, 'missing' ) );
+ } else {
+ if ( count( $states ) ) {
+ $exceptions .= self::makeComment(
+ 'Problematic modules: ' . FormatJson::encode( $states, ResourceLoader::inDebugMode() )
+ );
}
}
@@ -874,23 +1025,21 @@ class ResourceLoader {
/* Static Methods */
/**
- * Returns JS code to call to mw.loader.implement for a module with
- * given properties.
+ * Return JS code that calls mw.loader.implement with given module properties.
*
* @param string $name Module name
- * @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code
- * @param $styles Mixed: Array of CSS strings keyed by media type, or an array of lists of URLs to
- * CSS files keyed by media type
- * @param $messages Mixed: List of messages associated with this module. May either be an
- * associative array mapping message key to value, or a JSON-encoded message blob containing
- * the same data, wrapped in an XmlJsCode object.
- *
+ * @param mixed $scripts List of URLs to JavaScript files or String of JavaScript code
+ * @param mixed $styles Array of CSS strings keyed by media type, or an array of lists of URLs
+ * to CSS files keyed by media type
+ * @param mixed $messages List of messages associated with this module. May either be an
+ * associative array mapping message key to value, or a JSON-encoded message blob containing
+ * the same data, wrapped in an XmlJsCode object.
* @throws MWException
* @return string
*/
public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
if ( is_string( $scripts ) ) {
- $scripts = new XmlJsCode( "function () {\n{$scripts}\n}" );
+ $scripts = new XmlJsCode( "function ( $, jQuery ) {\n{$scripts}\n}" );
} elseif ( !is_array( $scripts ) ) {
throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
}
@@ -914,24 +1063,26 @@ class ResourceLoader {
/**
* Returns JS code which, when called, will register a given list of messages.
*
- * @param $messages Mixed: Either an associative array mapping message key to value, or a
- * JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
- *
+ * @param mixed $messages Either an associative array mapping message key to value, or a
+ * JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
* @return string
*/
public static function makeMessageSetScript( $messages ) {
- return Xml::encodeJsCall( 'mw.messages.set', array( (object)$messages ) );
+ return Xml::encodeJsCall(
+ 'mw.messages.set',
+ array( (object)$messages ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
* Combines an associative array mapping media type to CSS into a
* single stylesheet with "@media" blocks.
*
- * @param array $stylePairs Array keyed by media type containing (arrays of) CSS strings.
- *
- * @return Array
+ * @param array $stylePairs Array keyed by media type containing (arrays of) CSS strings
+ * @return array
*/
- private static function makeCombinedStyles( array $stylePairs ) {
+ public static function makeCombinedStyles( array $stylePairs ) {
$out = array();
foreach ( $stylePairs as $media => $styles ) {
// ResourceLoaderFileModule::getStyle can return the styles
@@ -968,16 +1119,23 @@ class ResourceLoader {
* - ResourceLoader::makeLoaderStateScript( array( $name => $state, ... ) ):
* Set the state of modules with the given names to the given states
*
- * @param $name string
- * @param $state
- *
+ * @param string $name
+ * @param string $state
* @return string
*/
public static function makeLoaderStateScript( $name, $state = null ) {
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mw.loader.state', array( $name ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.state',
+ array( $name ),
+ ResourceLoader::inDebugMode()
+ );
} else {
- return Xml::encodeJsCall( 'mw.loader.state', array( $name, $state ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.state',
+ array( $name, $state ),
+ ResourceLoader::inDebugMode()
+ );
}
}
@@ -988,55 +1146,67 @@ class ResourceLoader {
* and $group as supplied.
*
* @param string $name Module name
- * @param $version Integer: Module version number as a timestamp
+ * @param int $version Module version number as a timestamp
* @param array $dependencies List of module names on which this module depends
* @param string $group Group which the module is in.
* @param string $source Source of the module, or 'local' if not foreign.
* @param string $script JavaScript code
- *
* @return string
*/
- public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $source, $script ) {
+ public static function makeCustomLoaderScript( $name, $version, $dependencies,
+ $group, $source, $script
+ ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
return Xml::encodeJsCall(
"( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
- array( $name, $version, $dependencies, $group, $source ) );
+ array( $name, $version, $dependencies, $group, $source ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
* Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
*
- * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group, $source ):
- * Register a single module.
+ * - ResourceLoader::makeLoaderRegisterScript( $name, $version,
+ * $dependencies, $group, $source, $skip
+ * ):
+ * Register a single module.
*
* - ResourceLoader::makeLoaderRegisterScript( array( $name1, $name2 ) ):
- * Register modules with the given names.
+ * Register modules with the given names.
*
* - ResourceLoader::makeLoaderRegisterScript( array(
- * array( $name1, $version1, $dependencies1, $group1, $source1 ),
- * array( $name2, $version2, $dependencies1, $group2, $source2 ),
+ * array( $name1, $version1, $dependencies1, $group1, $source1, $skip1 ),
+ * array( $name2, $version2, $dependencies1, $group2, $source2, $skip2 ),
* ...
* ) ):
* Registers modules with the given names and parameters.
*
* @param string $name Module name
- * @param $version Integer: Module version number as a timestamp
+ * @param int $version Module version number as a timestamp
* @param array $dependencies List of module names on which this module depends
- * @param string $group group which the module is in.
- * @param string $source source of the module, or 'local' if not foreign
- *
+ * @param string $group Group which the module is in
+ * @param string $source Source of the module, or 'local' if not foreign
+ * @param string $skip Script body of the skip function
* @return string
*/
public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null, $source = null
+ $dependencies = null, $group = null, $source = null, $skip = null
) {
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.register',
+ array( $name ),
+ ResourceLoader::inDebugMode()
+ );
} else {
$version = (int)$version > 1 ? (int)$version : 1;
- return Xml::encodeJsCall( 'mw.loader.register',
- array( $name, $version, $dependencies, $group, $source ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.register',
+ array( $name, $version, $dependencies, $group, $source, $skip ),
+ ResourceLoader::inDebugMode()
+ );
}
}
@@ -1047,19 +1217,26 @@ class ResourceLoader {
* - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
* Register a single source
*
- * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $props1, $id2 => $props2, ... ) );
+ * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $loadUrl, $id2 => $loadUrl, ... ) );
* Register sources with the given IDs and properties.
*
- * @param string $id source ID
- * @param array $properties source properties (see addSource())
- *
+ * @param string $id Source ID
+ * @param array $properties Source properties (see addSource())
* @return string
*/
public static function makeLoaderSourcesScript( $id, $properties = null ) {
if ( is_array( $id ) ) {
- return Xml::encodeJsCall( 'mw.loader.addSource', array( $id ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.addSource',
+ array( $id ),
+ ResourceLoader::inDebugMode()
+ );
} else {
- return Xml::encodeJsCall( 'mw.loader.addSource', array( $id, $properties ) );
+ return Xml::encodeJsCall(
+ 'mw.loader.addSource',
+ array( $id, $properties ),
+ ResourceLoader::inDebugMode()
+ );
}
}
@@ -1068,7 +1245,6 @@ class ResourceLoader {
* present.
*
* @param string $script JavaScript code
- *
* @return string
*/
public static function makeLoaderConditionalScript( $script ) {
@@ -1080,11 +1256,14 @@ class ResourceLoader {
* the given value.
*
* @param array $configuration List of configuration values keyed by variable name
- *
* @return string
*/
public static function makeConfigSetScript( array $configuration ) {
- return Xml::encodeJsCall( 'mw.config.set', array( $configuration ), ResourceLoader::inDebugMode() );
+ return Xml::encodeJsCall(
+ 'mw.config.set',
+ array( $configuration ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
@@ -1092,7 +1271,7 @@ class ResourceLoader {
*
* For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' )
* becomes 'foo.bar,baz|bar.baz,quux'
- * @param array $modules of module names (strings)
+ * @param array $modules List of module names (strings)
* @return string Packed query string
*/
public static function makePackedModulesString( $modules ) {
@@ -1119,18 +1298,50 @@ class ResourceLoader {
* @return bool
*/
public static function inDebugMode() {
- global $wgRequest, $wgResourceLoaderDebug;
- static $retval = null;
- if ( !is_null( $retval ) ) {
- return $retval;
+ if ( self::$debugMode === null ) {
+ global $wgRequest, $wgResourceLoaderDebug;
+ self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
+ $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
+ );
}
- return $retval = $wgRequest->getFuzzyBool( 'debug',
- $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug ) );
+ return self::$debugMode;
+ }
+
+ /**
+ * Reset static members used for caching.
+ *
+ * Global state and $wgRequest are evil, but we're using it right
+ * now and sometimes we need to be able to force ResourceLoader to
+ * re-evaluate the context because it has changed (e.g. in the test suite).
+ */
+ public static function clearCache() {
+ self::$debugMode = null;
}
/**
* Build a load.php URL
- * @param array $modules of module names (strings)
+ *
+ * @since 1.24
+ * @param string $source Name of the ResourceLoader source
+ * @param ResourceLoaderContext $context
+ * @param array $extraQuery
+ * @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
+ */
+ public function createLoaderURL( $source, ResourceLoaderContext $context,
+ $extraQuery = array()
+ ) {
+ $query = self::createLoaderQuery( $context, $extraQuery );
+ $script = $this->getLoadScript( $source );
+
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ return wfExpandUrl( wfAppendQuery( $script, $query ) . '&*', PROTO_RELATIVE );
+ }
+
+ /**
+ * Build a load.php URL
+ * @deprecated since 1.24, use createLoaderURL instead
+ * @param array $modules Array of module names (strings)
* @param string $lang Language code
* @param string $skin Skin name
* @param string|null $user User name. If null, the &user= parameter is omitted
@@ -1142,9 +1353,12 @@ class ResourceLoader {
* @param array $extraQuery Extra query parameters to add
* @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
*/
- public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
- $printable = false, $handheld = false, $extraQuery = array() ) {
+ public static function makeLoaderURL( $modules, $lang, $skin, $user = null,
+ $version = null, $debug = false, $only = null, $printable = false,
+ $handheld = false, $extraQuery = array()
+ ) {
global $wgLoadScript;
+
$query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug,
$only, $printable, $handheld, $extraQuery
);
@@ -1155,6 +1369,30 @@ class ResourceLoader {
}
/**
+ * Helper for createLoaderURL()
+ *
+ * @since 1.24
+ * @see makeLoaderQuery
+ * @param ResourceLoaderContext $context
+ * @param array $extraQuery
+ * @return array
+ */
+ public static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = array() ) {
+ return self::makeLoaderQuery(
+ $context->getModules(),
+ $context->getLanguage(),
+ $context->getSkin(),
+ $context->getUser(),
+ $context->getVersion(),
+ $context->getDebug(),
+ $context->getOnly(),
+ $context->getRequest()->getBool( 'printable' ),
+ $context->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
+ }
+
+ /**
* Build a query array (array representation of query string) for load.php. Helper
* function for makeLoaderURL().
*
@@ -1171,8 +1409,10 @@ class ResourceLoader {
*
* @return array
*/
- public static function makeLoaderQuery( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
- $printable = false, $handheld = false, $extraQuery = array() ) {
+ public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
+ $version = null, $debug = false, $only = null, $printable = false,
+ $handheld = false, $extraQuery = array()
+ ) {
$query = array(
'modules' => self::makePackedModulesString( $modules ),
'lang' => $lang,
@@ -1217,12 +1457,12 @@ class ResourceLoader {
/**
* 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.
@@ -1232,9 +1472,9 @@ class ResourceLoader {
$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;
@@ -1243,18 +1483,14 @@ class ResourceLoader {
/**
* Get global LESS variables.
*
- * $since 1.22
- * @return array: Map of variable names to string CSS values.
+ * @param Config $config
+ * @since 1.22
+ * @return array Map of variable names to string CSS values.
*/
- public static function getLESSVars() {
- global $wgResourceLoaderLESSVars;
-
- static $lessVars = null;
- if ( $lessVars === null ) {
- $lessVars = $wgResourceLoaderLESSVars;
- // Sort by key to ensure consistent hashing for cache lookups.
- ksort( $lessVars );
- }
+ public static function getLessVars( Config $config ) {
+ $lessVars = $config->get( 'ResourceLoaderLESSVars' );
+ // Sort by key to ensure consistent hashing for cache lookups.
+ ksort( $lessVars );
return $lessVars;
}
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index 22ff6a7e..7af7b898 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -27,7 +27,6 @@
* of a specific loader request
*/
class ResourceLoaderContext {
-
/* Protected Members */
protected $resourceLoader;
@@ -46,12 +45,10 @@ class ResourceLoaderContext {
/* Methods */
/**
- * @param $resourceLoader ResourceLoader
- * @param $request WebRequest
+ * @param ResourceLoader $resourceLoader
+ * @param WebRequest $request
*/
- public function __construct( $resourceLoader, WebRequest $request ) {
- global $wgDefaultSkin, $wgResourceLoaderDebug;
-
+ public function __construct( ResourceLoader $resourceLoader, WebRequest $request ) {
$this->resourceLoader = $resourceLoader;
$this->request = $request;
@@ -62,7 +59,9 @@ class ResourceLoaderContext {
// Various parameters
$this->skin = $request->getVal( 'skin' );
$this->user = $request->getVal( 'user' );
- $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug );
+ $this->debug = $request->getFuzzyBool(
+ 'debug', $resourceLoader->getConfig()->get( 'ResourceLoaderDebug' )
+ );
$this->only = $request->getVal( 'only' );
$this->version = $request->getVal( 'version' );
$this->raw = $request->getFuzzyBool( 'raw' );
@@ -70,7 +69,7 @@ class ResourceLoaderContext {
$skinnames = Skin::getSkinNames();
// If no skin is specified, or we don't recognize the skin, use the default skin
if ( !$this->skin || !isset( $skinnames[$this->skin] ) ) {
- $this->skin = $wgDefaultSkin;
+ $this->skin = $resourceLoader->getConfig()->get( 'DefaultSkin' );
}
}
@@ -79,12 +78,10 @@ class ResourceLoaderContext {
* an array of module names like array( 'jquery.foo', 'jquery.bar',
* 'jquery.ui.baz', 'jquery.ui.quux' )
* @param string $modules Packed module name list
- * @return array of module names
+ * @return array Array of module names
*/
public static function expandModuleNames( $modules ) {
$retval = array();
- // For backwards compatibility with an earlier hack, replace ! with .
- $modules = str_replace( '!', '.', $modules );
$exploded = explode( '|', $modules );
foreach ( $exploded as $group ) {
if ( strpos( $group, ',' ) === false ) {
@@ -111,11 +108,14 @@ class ResourceLoaderContext {
}
/**
- * Return a dummy ResourceLoaderContext object suitable for passing into things that don't "really" need a context
+ * Return a dummy ResourceLoaderContext object suitable for passing into
+ * things that don't "really" need a context.
* @return ResourceLoaderContext
*/
public static function newDummyContext() {
- return new self( null, new FauxRequest( array() ) );
+ return new self( new ResourceLoader(
+ ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
+ ), new FauxRequest( array() ) );
}
/**
@@ -144,11 +144,8 @@ class ResourceLoaderContext {
*/
public function getLanguage() {
if ( $this->language === null ) {
- global $wgLang;
- $this->language = $this->request->getVal( 'lang' );
- if ( !$this->language ) {
- $this->language = $wgLang->getCode();
- }
+ // Must be a valid language code after this point (bug 62849)
+ $this->language = RequestContext::sanitizeLangCode( $this->request->getVal( 'lang' ) );
}
return $this->language;
}
@@ -160,7 +157,7 @@ class ResourceLoaderContext {
if ( $this->direction === null ) {
$this->direction = $this->request->getVal( 'dir' );
if ( !$this->direction ) {
- # directionality based on user language (see bug 6100)
+ // Determine directionality based on user language (bug 6100)
$this->direction = Language::factory( $this->getLanguage() )->getDir();
}
}
@@ -189,14 +186,14 @@ class ResourceLoaderContext {
}
/**
- * @return String|null
+ * @return string|null
*/
public function getOnly() {
return $this->only;
}
/**
- * @return String|null
+ * @return string|null
*/
public function getVersion() {
return $this->version;
@@ -213,21 +210,21 @@ class ResourceLoaderContext {
* @return bool
*/
public function shouldIncludeScripts() {
- return is_null( $this->only ) || $this->only === 'scripts';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'scripts';
}
/**
* @return bool
*/
public function shouldIncludeStyles() {
- return is_null( $this->only ) || $this->only === 'styles';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'styles';
}
/**
* @return bool
*/
public function shouldIncludeMessages() {
- return is_null( $this->only ) || $this->only === 'messages';
+ return is_null( $this->getOnly() ) || $this->getOnly() === 'messages';
}
/**
@@ -236,8 +233,8 @@ class ResourceLoaderContext {
public function getHash() {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
- $this->getLanguage(), $this->getDirection(), $this->skin, $this->user,
- $this->debug, $this->only, $this->version
+ $this->getLanguage(), $this->getDirection(), $this->getSkin(), $this->getUser(),
+ $this->getDebug(), $this->getOnly(), $this->getVersion()
) );
}
return $this->hash;
diff --git a/includes/resourceloader/ResourceLoaderEditToolbarModule.php b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
new file mode 100644
index 00000000..2e07911c
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Resource loader module for the edit toolbar.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * ResourceLoader module for the edit toolbar.
+ *
+ * @since 1.24
+ */
+class ResourceLoaderEditToolbarModule extends ResourceLoaderFileModule {
+ /**
+ * Serialize a string (escape and quote) for use as a CSS string value.
+ * http://www.w3.org/TR/2013/WD-cssom-20131205/#serialize-a-string
+ *
+ * @param string $value
+ * @return string
+ */
+ private static function cssSerializeString( $value ) {
+ if ( strstr( $value, "\0" ) ) {
+ throw new Exception( "Invalid character in CSS string" );
+ }
+ $value = strtr( $value, array( '\\' => '\\\\', '"' => '\\"' ) );
+ $value = preg_replace_callback( '/[\x01-\x1f\x7f-\x9f]/', function ( $match ) {
+ return '\\' . base_convert( ord( $match[0] ), 10, 16 ) . ' ';
+ }, $value );
+ return '"' . $value . '"';
+ }
+
+ /**
+ * Get language-specific LESS variables for this module.
+ *
+ * @return array
+ */
+ private function getLessVars( ResourceLoaderContext $context ) {
+ $language = Language::factory( $context->getLanguage() );
+
+ // This is very conveniently formatted and we can pass it right through
+ $vars = $language->getImageFiles();
+
+ // lessc tries to be helpful and parse our variables as LESS source code
+ foreach ( $vars as $key => &$value ) {
+ $value = self::cssSerializeString( $value );
+ }
+
+ return $vars;
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return max(
+ parent::getModifiedTime( $context ),
+ $this->getHashMtime( $context )
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5(
+ parent::getModifiedHash( $context ) .
+ serialize( $this->getLessVars( $context ) )
+ );
+ }
+
+ /**
+ * Get a LESS compiler instance for this module.
+ *
+ * Set our variables in it.
+ *
+ * @throws MWException
+ * @param ResourceLoaderContext $context
+ * @return lessc
+ */
+ protected function getLessCompiler( ResourceLoaderContext $context = null ) {
+ $compiler = parent::getLessCompiler();
+ $compiler->setVariables( $this->getLessVars( $context ) );
+ return $compiler;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index 9ed181ed..dc8b14a2 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -26,111 +26,131 @@
* ResourceLoader module based on local JavaScript/CSS files.
*/
class ResourceLoaderFileModule extends ResourceLoaderModule {
-
/* Protected Members */
- /** String: Local base path, see __construct() */
+ /** @var string Local base path, see __construct() */
protected $localBasePath = '';
- /** String: Remote base path, see __construct() */
+
+ /** @var string Remote base path, see __construct() */
protected $remoteBasePath = '';
+
/**
- * Array: List of paths to JavaScript files to always include
+ * @var array List of paths to JavaScript files to always include
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $scripts = array();
+
/**
- * Array: List of JavaScript files to include when using a specific language
+ * @var array List of JavaScript files to include when using a specific language
* @par Usage:
* @code
* array( [language-code] => array( [file-path], [file-path], ... ), ... )
* @endcode
*/
protected $languageScripts = array();
+
/**
- * Array: List of JavaScript files to include when using a specific skin
+ * @var array List of JavaScript files to include when using a specific skin
* @par Usage:
* @code
* array( [skin-name] => array( [file-path], [file-path], ... ), ... )
* @endcode
*/
protected $skinScripts = array();
+
/**
- * Array: List of paths to JavaScript files to include in debug mode
+ * @var array List of paths to JavaScript files to include in debug mode
* @par Usage:
* @code
* array( [skin-name] => array( [file-path], [file-path], ... ), ... )
* @endcode
*/
protected $debugScripts = array();
+
/**
- * Array: List of paths to JavaScript files to include in the startup module
+ * @var array List of paths to JavaScript files to include in the startup module
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $loaderScripts = array();
+
/**
- * Array: List of paths to CSS files to always include
+ * @var array List of paths to CSS files to always include
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $styles = array();
+
/**
- * Array: List of paths to CSS files to include when using specific skins
+ * @var array List of paths to CSS files to include when using specific skins
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $skinStyles = array();
+
/**
- * Array: List of modules this module depends on
+ * @var array List of modules this module depends on
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
* @endcode
*/
protected $dependencies = array();
+
+ /**
+ * @var string File name containing the body of the skip function
+ */
+ protected $skipFunction = null;
+
/**
- * Array: List of message keys used by this module
+ * @var array List of message keys used by this module
* @par Usage:
* @code
* array( [message-key], [message-key], ... )
* @endcode
*/
protected $messages = array();
- /** String: Name of group to load this module in */
+
+ /** @var string Name of group to load this module in */
protected $group;
- /** String: Position on the page to load this module at */
+
+ /** @var string Position on the page to load this module at */
protected $position = 'bottom';
- /** Boolean: Link to raw files in debug mode */
+
+ /** @var bool Link to raw files in debug mode */
protected $debugRaw = true;
- /** Boolean: Whether mw.loader.state() call should be omitted */
+
+ /** @var bool Whether mw.loader.state() call should be omitted */
protected $raw = false;
+
protected $targets = array( 'desktop' );
/**
- * Boolean: Whether getStyleURLsForDebug should return raw file paths,
+ * @var bool Whether getStyleURLsForDebug should return raw file paths,
* or return load.php urls
*/
protected $hasGeneratedStyles = false;
/**
- * Array: Cache for mtime
+ * @var array Cache for mtime
* @par Usage:
* @code
* array( [hash] => [mtime], [hash] => [mtime], ... )
* @endcode
*/
protected $modifiedTime = array();
+
/**
- * Array: Place where readStyleFile() tracks file dependencies
+ * @var array Place where readStyleFile() tracks file dependencies
* @par Usage:
* @code
* array( [file-path], [file-path], ... )
@@ -148,7 +168,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
* to $IP
* @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults
- * to $wgScriptPath
+ * to $wgResourceBasePath
*
* Below is a description for the $options array:
* @throws MWException
@@ -157,10 +177,12 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* array(
* // Base path to prepend to all local paths in $options. Defaults to $IP
* 'localBasePath' => [base path],
- * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath
+ * // Base path to prepend to all remote paths in $options. Defaults to $wgResourceBasePath
* 'remoteBasePath' => [base path],
* // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath
* 'remoteExtPath' => [base path],
+ * // Equivalent of remoteBasePath, but relative to $wgStylePath
+ * 'remoteSkinPath' => [base path],
* // Scripts to always include
* 'scripts' => [file path string or array of file path strings],
* // Scripts to include in specific language contexts
@@ -189,25 +211,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* 'group' => [group name string],
* // Position on the page to load this module at
* 'position' => ['bottom' (default) or 'top']
+ * // Function that, if it returns true, makes the loader skip this module.
+ * // The file must contain valid JavaScript for execution in a private function.
+ * // The file must not contain the "function () {" and "}" wrapper though.
+ * 'skipFunction' => [file path]
* )
* @endcode
*/
- public function __construct( $options = array(), $localBasePath = null,
+ public function __construct(
+ $options = array(),
+ $localBasePath = null,
$remoteBasePath = null
) {
- global $IP, $wgScriptPath, $wgResourceBasePath;
- $this->localBasePath = $localBasePath === null ? $IP : $localBasePath;
- if ( $remoteBasePath !== null ) {
- $this->remoteBasePath = $remoteBasePath;
- } else {
- $this->remoteBasePath = $wgResourceBasePath === null ? $wgScriptPath : $wgResourceBasePath;
- }
-
- if ( isset( $options['remoteExtPath'] ) ) {
- global $wgExtensionAssetsPath;
- $this->remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
- }
+ // localBasePath and remoteBasePath both have unbelievably long fallback chains
+ // and need to be handled separately.
+ list( $this->localBasePath, $this->remoteBasePath ) =
+ self::extractBasePaths( $options, $localBasePath, $remoteBasePath );
+ // Extract, validate and normalise remaining options
foreach ( $options as $member => $option ) {
switch ( $member ) {
// Lists of file paths
@@ -241,13 +262,16 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'dependencies':
case 'messages':
case 'targets':
- $this->{$member} = (array)$option;
+ // Normalise
+ $option = array_values( array_unique( (array)$option ) );
+ sort( $option );
+
+ $this->{$member} = $option;
break;
// Single strings
case 'group':
case 'position':
- case 'localBasePath':
- case 'remoteBasePath':
+ case 'skipFunction':
$this->{$member} = (string)$option;
break;
// Single booleans
@@ -257,16 +281,64 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
}
}
+ }
+
+ /**
+ * Extract a pair of local and remote base paths from module definition information.
+ * Implementation note: the amount of global state used in this function is staggering.
+ *
+ * @param array $options Module definition
+ * @param string $localBasePath Path to use if not provided in module definition. Defaults
+ * to $IP
+ * @param string $remoteBasePath Path to use if not provided in module definition. Defaults
+ * to $wgResourceBasePath
+ * @return array Array( localBasePath, remoteBasePath )
+ */
+ public static function extractBasePaths(
+ $options = array(),
+ $localBasePath = null,
+ $remoteBasePath = null
+ ) {
+ global $IP, $wgResourceBasePath;
+
+ // The different ways these checks are done, and their ordering, look very silly,
+ // but were preserved for backwards-compatibility just in case. Tread lightly.
+
+ $localBasePath = $localBasePath === null ? $IP : $localBasePath;
+ if ( $remoteBasePath === null ) {
+ $remoteBasePath = $wgResourceBasePath;
+ }
+
+ if ( isset( $options['remoteExtPath'] ) ) {
+ global $wgExtensionAssetsPath;
+ $remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
+ }
+
+ if ( isset( $options['remoteSkinPath'] ) ) {
+ global $wgStylePath;
+ $remoteBasePath = $wgStylePath . '/' . $options['remoteSkinPath'];
+ }
+
+ if ( array_key_exists( 'localBasePath', $options ) ) {
+ $localBasePath = (string)$options['localBasePath'];
+ }
+
+ if ( array_key_exists( 'remoteBasePath', $options ) ) {
+ $remoteBasePath = (string)$options['remoteBasePath'];
+ }
+
// Make sure the remote base path is a complete valid URL,
// but possibly protocol-relative to avoid cache pollution
- $this->remoteBasePath = wfExpandUrl( $this->remoteBasePath, PROTO_RELATIVE );
+ $remoteBasePath = wfExpandUrl( $remoteBasePath, PROTO_RELATIVE );
+
+ return array( $localBasePath, $remoteBasePath );
}
/**
* Gets all scripts for a given context concatenated together.
*
* @param ResourceLoaderContext $context Context in which to generate script
- * @return string: JavaScript code for $context
+ * @return string JavaScript code for $context
*/
public function getScript( ResourceLoaderContext $context ) {
$files = $this->getScriptFiles( $context );
@@ -293,27 +365,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * Gets loader script.
+ * Get loader script.
*
- * @return string: JavaScript code to be added to startup module
+ * @return string|bool JavaScript code to be added to startup module
*/
public function getLoaderScript() {
- if ( count( $this->loaderScripts ) == 0 ) {
+ if ( count( $this->loaderScripts ) === 0 ) {
return false;
}
return $this->readScriptFiles( $this->loaderScripts );
}
/**
- * Gets all styles for a given context concatenated together.
+ * Get all styles for a given context.
*
- * @param ResourceLoaderContext $context Context in which to generate styles
- * @return string: CSS code for $context
+ * @param ResourceLoaderContext $context
+ * @return array CSS code for $context as an associative array mapping media type to CSS text.
*/
public function getStyles( ResourceLoaderContext $context ) {
$styles = $this->readStyleFiles(
$this->getStyleFiles( $context ),
- $this->getFlip( $context )
+ $this->getFlip( $context ),
+ $context
);
// Collect referenced files
$this->localFileRefs = array_unique( $this->localFileRefs );
@@ -360,7 +433,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets list of message keys used by this module.
*
- * @return array: List of message keys
+ * @return array List of message keys
*/
public function getMessages() {
return $this->messages;
@@ -369,7 +442,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the name of the group this module should be loaded in.
*
- * @return string: Group name
+ * @return string Group name
*/
public function getGroup() {
return $this->group;
@@ -385,13 +458,34 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets list of names of modules this module depends on.
*
- * @return array: List of module names
+ * @return array List of module names
*/
public function getDependencies() {
return $this->dependencies;
}
/**
+ * Get the skip function.
+ *
+ * @return string|null
+ */
+ public function getSkipFunction() {
+ if ( !$this->skipFunction ) {
+ return null;
+ }
+
+ $localPath = $this->getLocalPath( $this->skipFunction );
+ if ( !file_exists( $localPath ) ) {
+ throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" );
+ }
+ $contents = file_get_contents( $localPath );
+ if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
+ $contents = $this->validateScriptFile( $localPath, $contents );
+ }
+ return $contents;
+ }
+
+ /**
* @return bool
*/
public function isRaw() {
@@ -409,7 +503,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param ResourceLoaderContext $context Context in which to calculate
* the modified time
- * @return int: UNIX timestamp
+ * @return int UNIX timestamp
* @see ResourceLoaderModule::getFileDependencies
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
@@ -425,10 +519,11 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
foreach ( $styles as $styleFiles ) {
$files = array_merge( $files, $styleFiles );
}
- $skinFiles = self::tryForKey(
- self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ),
- $context->getSkin(),
- 'default'
+
+ $skinFiles = self::collateFilePathListByOption(
+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
+ 'media',
+ 'all'
);
foreach ( $skinFiles as $styleFiles ) {
$files = array_merge( $files, $styleFiles );
@@ -443,6 +538,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
$this->loaderScripts
);
+ if ( $this->skipFunction ) {
+ $files[] = $this->skipFunction;
+ }
$files = array_map( array( $this, 'getLocalPath' ), $files );
// File deps need to be treated separately because they're already prefixed
$files = array_merge( $files, $this->getFileDependencies( $context->getSkin() ) );
@@ -450,36 +548,82 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
// If a module is nothing but a list of dependencies, we need to avoid
// giving max() an empty array
if ( count( $files ) === 0 ) {
+ $this->modifiedTime[$context->getHash()] = 1;
wfProfileOut( __METHOD__ );
- return $this->modifiedTime[$context->getHash()] = 1;
+ return $this->modifiedTime[$context->getHash()];
}
wfProfileIn( __METHOD__ . '-filemtime' );
$filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
wfProfileOut( __METHOD__ . '-filemtime' );
+
$this->modifiedTime[$context->getHash()] = max(
$filesMtime,
- $this->getMsgBlobMtime( $context->getLanguage() ) );
+ $this->getMsgBlobMtime( $context->getLanguage() ),
+ $this->getDefinitionMtime( $context )
+ );
wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()];
}
+ /**
+ * Get the definition summary for this module.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getDefinitionSummary( ResourceLoaderContext $context ) {
+ $summary = array(
+ 'class' => get_class( $this ),
+ );
+ foreach ( array(
+ 'scripts',
+ 'debugScripts',
+ 'loaderScripts',
+ 'styles',
+ 'languageScripts',
+ 'skinScripts',
+ 'skinStyles',
+ 'dependencies',
+ 'messages',
+ 'targets',
+ 'group',
+ 'position',
+ 'skipFunction',
+ 'localBasePath',
+ 'remoteBasePath',
+ 'debugRaw',
+ 'raw',
+ ) as $member ) {
+ $summary[$member] = $this->{$member};
+ };
+ return $summary;
+ }
+
/* Protected Methods */
/**
- * @param string $path
+ * @param string|ResourceLoaderFilePath $path
* @return string
*/
protected function getLocalPath( $path ) {
+ if ( $path instanceof ResourceLoaderFilePath ) {
+ return $path->getLocalPath();
+ }
+
return "{$this->localBasePath}/$path";
}
/**
- * @param string $path
+ * @param string|ResourceLoaderFilePath $path
* @return string
*/
protected function getRemotePath( $path ) {
+ if ( $path instanceof ResourceLoaderFilePath ) {
+ return $path->getRemotePath();
+ }
+
return "{$this->remoteBasePath}/$path";
}
@@ -488,7 +632,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @since 1.22
* @param string $path
- * @return string: the stylesheet language name
+ * @return string The stylesheet language name
*/
public function getStyleSheetLang( $path ) {
return preg_match( '/\.less$/i', $path ) ? 'less' : 'css';
@@ -499,9 +643,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param array $list List of file paths in any combination of index/path
* or path/options pairs
- * @param string $option option name
- * @param mixed $default default value if the option isn't set
- * @return array: List of file paths, collated by $option
+ * @param string $option Option name
+ * @param mixed $default Default value if the option isn't set
+ * @return array List of file paths, collated by $option
*/
protected static function collateFilePathListByOption( array $list, $option, $default ) {
$collatedFiles = array();
@@ -525,31 +669,31 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
- * Gets a list of element that match a key, optionally using a fallback key.
+ * Get a list of element that match a key, optionally using a fallback key.
*
* @param array $list List of lists to select from
* @param string $key Key to look for in $map
* @param string $fallback Key to look for in $list if $key doesn't exist
- * @return array: List of elements from $map which matched $key or $fallback,
- * or an empty list in case of no match
+ * @return array List of elements from $map which matched $key or $fallback,
+ * or an empty list in case of no match
*/
protected static function tryForKey( array $list, $key, $fallback = null ) {
if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
return $list[$key];
} elseif ( is_string( $fallback )
&& isset( $list[$fallback] )
- && is_array( $list[$fallback] ) )
- {
+ && is_array( $list[$fallback] )
+ ) {
return $list[$fallback];
}
return array();
}
/**
- * Gets a list of file paths for all scripts in this module, in order of propper execution.
+ * Get a list of file paths for all scripts in this module, in order of proper execution.
*
* @param ResourceLoaderContext $context
- * @return array: List of file paths
+ * @return array List of file paths
*/
protected function getScriptFiles( ResourceLoaderContext $context ) {
$files = array_merge(
@@ -561,39 +705,82 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$files = array_merge( $files, $this->debugScripts );
}
- return array_unique( $files );
+ return array_unique( $files, SORT_REGULAR );
}
/**
- * Gets a list of file paths for all styles in this module, in order of propper inclusion.
+ * Get a list of file paths for all styles in this module, in order of proper inclusion.
*
* @param ResourceLoaderContext $context
- * @return array: List of file paths
+ * @return array List of file paths
*/
- protected function getStyleFiles( ResourceLoaderContext $context ) {
+ public function getStyleFiles( ResourceLoaderContext $context ) {
return array_merge_recursive(
self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
self::collateFilePathListByOption(
- self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 'media', 'all'
+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
+ 'media',
+ 'all'
)
);
}
/**
- * Returns all style files used by this module
+ * Gets a list of file paths for all skin styles in the module used by
+ * the skin.
+ *
+ * @param string $skinName The name of the skin
+ * @return array A list of file paths collated by media type
+ */
+ protected function getSkinStyleFiles( $skinName ) {
+ return self::collateFilePathListByOption(
+ self::tryForKey( $this->skinStyles, $skinName ),
+ 'media',
+ 'all'
+ );
+ }
+
+ /**
+ * Gets a list of file paths for all skin style files in the module,
+ * for all available skins.
+ *
+ * @return array A list of file paths collated by media type
+ */
+ protected function getAllSkinStyleFiles() {
+ $styleFiles = array();
+ $internalSkinNames = array_keys( Skin::getSkinNames() );
+ $internalSkinNames[] = 'default';
+
+ foreach ( $internalSkinNames as $internalSkinName ) {
+ $styleFiles = array_merge_recursive(
+ $styleFiles,
+ $this->getSkinStyleFiles( $internalSkinName )
+ );
+ }
+
+ return $styleFiles;
+ }
+
+ /**
+ * Returns all style files and all skin style files used by this module.
+ *
* @return array
*/
public function getAllStyleFiles() {
- $files = array();
- foreach( (array)$this->styles as $key => $value ) {
- if ( is_array( $value ) ) {
- $path = $key;
- } else {
- $path = $value;
+ $collatedStyleFiles = array_merge_recursive(
+ self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
+ $this->getAllSkinStyleFiles()
+ );
+
+ $result = array();
+
+ foreach ( $collatedStyleFiles as $media => $styleFiles ) {
+ foreach ( $styleFiles as $styleFile ) {
+ $result[] = $this->getLocalPath( $styleFile );
}
- $files[] = $this->getLocalPath( $path );
}
- return $files;
+
+ return $result;
}
/**
@@ -601,21 +788,20 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param array $scripts List of file paths to scripts to read, remap and concetenate
* @throws MWException
- * @return string: Concatenated and remapped JavaScript data from $scripts
+ * @return string Concatenated and remapped JavaScript data from $scripts
*/
protected function readScriptFiles( array $scripts ) {
- global $wgResourceLoaderValidateStaticJS;
if ( empty( $scripts ) ) {
return '';
}
$js = '';
- foreach ( array_unique( $scripts ) as $fileName ) {
+ foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) {
$localPath = $this->getLocalPath( $fileName );
if ( !file_exists( $localPath ) ) {
throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
}
$contents = file_get_contents( $localPath );
- if ( $wgResourceLoaderValidateStaticJS ) {
+ if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
// Static files don't really need to be checked as often; unlike
// on-wiki module they shouldn't change unexpectedly without
// admin interference.
@@ -631,26 +817,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param array $styles List of media type/list of file paths pairs, to read, remap and
* concetenate
- *
* @param bool $flip
+ * @param ResourceLoaderContext $context (optional)
*
- * @return array: List of concatenated and remapped CSS data from $styles,
+ * @throws MWException
+ * @return array List of concatenated and remapped CSS data from $styles,
* keyed by media type
*/
- protected function readStyleFiles( array $styles, $flip ) {
+ public function readStyleFiles( array $styles, $flip, $context = null ) {
if ( empty( $styles ) ) {
return array();
}
foreach ( $styles as $media => $files ) {
- $uniqueFiles = array_unique( $files );
- $styles[$media] = implode(
- "\n",
- array_map(
- array( $this, 'readStyleFile' ),
- $uniqueFiles,
- array_fill( 0, count( $uniqueFiles ), $flip )
- )
- );
+ $uniqueFiles = array_unique( $files, SORT_REGULAR );
+ $styleFiles = array();
+ foreach ( $uniqueFiles as $file ) {
+ $styleFiles[] = $this->readStyleFile( $file, $flip, $context );
+ }
+ $styles[$media] = implode( "\n", $styleFiles );
}
return $styles;
}
@@ -662,20 +846,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* @param string $path File path of style file to read
* @param bool $flip
+ * @param ResourceLoaderContext $context (optional)
*
- * @return string: CSS data in script file
- * @throws MWException if the file doesn't exist
+ * @return string CSS data in script file
+ * @throws MWException If the file doesn't exist
*/
- protected function readStyleFile( $path, $flip ) {
+ protected function readStyleFile( $path, $flip, $context = null ) {
$localPath = $this->getLocalPath( $path );
+ $remotePath = $this->getRemotePath( $path );
if ( !file_exists( $localPath ) ) {
$msg = __METHOD__ . ": style file not found: \"$localPath\"";
wfDebugLog( 'resourceloader', $msg );
throw new MWException( $msg );
}
- if ( $this->getStyleSheetLang( $path ) === 'less' ) {
- $style = $this->compileLESSFile( $localPath );
+ if ( $this->getStyleSheetLang( $localPath ) === 'less' ) {
+ $compiler = $this->getLessCompiler( $context );
+ $style = $this->compileLessFile( $localPath, $compiler );
$this->hasGeneratedStyles = true;
} else {
$style = file_get_contents( $localPath );
@@ -684,20 +871,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
if ( $flip ) {
$style = CSSJanus::transform( $style, true, false );
}
- $dirname = dirname( $path );
- if ( $dirname == '.' ) {
- // If $path doesn't have a directory component, don't prepend a dot
- $dirname = '';
- }
- $dir = $this->getLocalPath( $dirname );
- $remoteDir = $this->getRemotePath( $dirname );
+ $localDir = dirname( $localPath );
+ $remoteDir = dirname( $remotePath );
// Get and register local file references
$this->localFileRefs = array_merge(
$this->localFileRefs,
- CSSMin::getLocalFileReferences( $style, $dir )
+ CSSMin::getLocalFileReferences( $style, $localDir )
);
return CSSMin::remap(
- $style, $dir, $remoteDir, true
+ $style, $localDir, $remoteDir, true
);
}
@@ -713,64 +895,43 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
*
- * @return array of strings
+ * @return array Array of strings
*/
public function getTargets() {
return $this->targets;
}
/**
- * Generate a cache key for a LESS file.
+ * Compile a LESS file into CSS.
*
- * The cache key varies on the file name and the names and values of global
- * LESS variables.
+ * Keeps track of all used files and adds them to localFileRefs.
*
* @since 1.22
- * @param string $fileName File name of root LESS file.
- * @return string: Cache key
+ * @throws Exception If lessc encounters a parse error
+ * @param string $fileName File path of LESS source
+ * @param lessc $compiler Compiler to use, if not default
+ * @return string CSS source
*/
- protected static function getLESSCacheKey( $fileName ) {
- $vars = json_encode( ResourceLoader::getLESSVars() );
- $hash = md5( $fileName . $vars );
- return wfMemcKey( 'resourceloader', 'less', $hash );
+ protected function compileLessFile( $fileName, $compiler = null ) {
+ if ( !$compiler ) {
+ $compiler = $this->getLessCompiler();
+ }
+ $result = $compiler->compileFile( $fileName );
+ $this->localFileRefs += array_keys( $compiler->allParsedFiles() );
+ return $result;
}
/**
- * Compile a LESS file into CSS.
+ * Get a LESS compiler instance for this module in given context.
*
- * If invalid, returns replacement CSS source consisting of the compilation
- * error message encoded as a comment. To save work, we cache a result object
- * which comprises the compiled CSS and the names & mtimes of the files
- * that were processed. lessphp compares the cached & current mtimes and
- * recompiles as necessary.
+ * Just calls ResourceLoader::getLessCompiler() by default to get a global compiler.
*
- * @since 1.22
- * @param string $fileName File path of LESS source
- * @return string: CSS source
+ * @param ResourceLoaderContext $context
+ * @throws MWException
+ * @since 1.24
+ * @return lessc
*/
- protected function compileLESSFile( $fileName ) {
- $key = self::getLESSCacheKey( $fileName );
- $cache = wfGetCache( CACHE_ANYTHING );
-
- // The input to lessc. Either an associative array representing the
- // cached results of a previous compilation, or the string file name if
- // no cache result exists.
- $source = $cache->get( $key );
- if ( !is_array( $source ) || !isset( $source['root'] ) ) {
- $source = $fileName;
- }
-
- $compiler = ResourceLoader::getLessCompiler();
- $result = null;
-
- $result = $compiler->cachedCompile( $source );
-
- if ( !is_array( $result ) ) {
- throw new MWException( 'LESS compiler result has type ' . gettype( $result ) . '; array expected.' );
- }
-
- $this->localFileRefs += array_keys( $result['files'] );
- $cache->set( $key, $result );
- return $result['compiled'];
+ protected function getLessCompiler( ResourceLoaderContext $context = null ) {
+ return ResourceLoader::getLessCompiler( $this->getConfig() );
}
}
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/resourceloader/ResourceLoaderFilePageModule.php
index 61ed5206..8c7fbe76 100644
--- a/includes/resourceloader/ResourceLoaderFilePageModule.php
+++ b/includes/resourceloader/ResourceLoaderFilePageModule.php
@@ -26,7 +26,7 @@
class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderFilePath.php b/includes/resourceloader/ResourceLoaderFilePath.php
new file mode 100644
index 00000000..dd239d09
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderFilePath.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * An object to represent a path to a JavaScript/CSS file, along with a remote
+ * and local base path, for use with ResourceLoaderFileModule.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * An object to represent a path to a JavaScript/CSS file, along with a remote
+ * and local base path, for use with ResourceLoaderFileModule.
+ */
+class ResourceLoaderFilePath {
+ /* Protected Members */
+
+ /** @var string Local base path */
+ protected $localBasePath;
+
+ /** @var string Remote base path */
+ protected $remoteBasePath;
+
+ /**
+ * @var string Path to the file */
+ protected $path;
+
+ /* Methods */
+
+ /**
+ * @param string $path Path to the file.
+ * @param string $localBasePath Base path to prepend when generating a local path.
+ * @param string $remoteBasePath Base path to prepend when generating a remote path.
+ */
+ public function __construct( $path, $localBasePath, $remoteBasePath ) {
+ $this->path = $path;
+ $this->localBasePath = $localBasePath;
+ $this->remoteBasePath = $remoteBasePath;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLocalPath() {
+ return "{$this->localBasePath}/{$this->path}";
+ }
+
+ /**
+ * @return string
+ */
+ public function getRemotePath() {
+ return "{$this->remoteBasePath}/{$this->path}";
+ }
+
+ /**
+ * @return string
+ */
+ public function getPath() {
+ return $this->path;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderLESSFunctions.php b/includes/resourceloader/ResourceLoaderLESSFunctions.php
deleted file mode 100644
index c7570f64..00000000
--- a/includes/resourceloader/ResourceLoaderLESSFunctions.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * PHP-provided functions for LESS; see docs for $wgResourceLoaderLESSFunctions
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-class ResourceLoaderLESSFunctions {
- /**
- * Check if an image file reference is suitable for embedding.
- * An image is embeddable if it (a) exists, (b) has a suitable MIME-type,
- * (c) does not exceed IE<9 size limit of 32kb. This is a LESS predicate
- * function; it returns a LESS boolean value and can thus be used as a
- * mixin guard.
- *
- * @par Example:
- * @code
- * .background-image(@url) when(embeddable(@url)) {
- * background-image: url(@url) !ie;
- * }
- * @endcode
- */
- public static function embeddable( $frame, $less ) {
- $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
- $url = $frame[2][0];
- $file = realpath( $base . '/' . $url );
- return $less->toBool( $file
- && strpos( $url, '//' ) === false
- && filesize( $file ) < CSSMin::EMBED_SIZE_LIMIT
- && CSSMin::getMimeType( $file ) !== false );
- }
-
- /**
- * Convert an image URI to a base64-encoded data URI.
- *
- * @par Example:
- * @code
- * .fancy-button {
- * background-image: embed('../images/button-bg.png');
- * }
- * @endcode
- */
- public static function embed( $frame, $less ) {
- $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME );
- $url = $frame[2][0];
- $file = realpath( $base . '/' . $url );
-
- $data = CSSMin::encodeImageAsDataURI( $file );
- $less->addParsedFile( $file );
- return 'url(' . $data . ')';
- }
-}
diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
index fa0fbf85..09d90d6e 100644
--- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
@@ -27,99 +27,51 @@
*/
class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
- protected $language;
protected $targets = array( 'desktop', 'mobile' );
- /**
- * Get the grammar forms for the site content language.
- *
- * @return array
- */
- protected function getSiteLangGrammarForms() {
- return $this->language->getGrammarForms();
- }
-
- /**
- * Get the plural forms for the site content language.
- *
- * @return array
- */
- protected function getPluralRules() {
- return $this->language->getPluralRules();
- }
-
- /**
- * Get the digit groupin Pattern for the site content language.
- *
- * @return array
- */
- protected function getDigitGroupingPattern() {
- return $this->language->digitGroupingPattern();
- }
-
- /**
- * Get the digit transform table for the content language
- *
- * @return array
- */
- protected function getDigitTransformTable() {
- return $this->language->digitTransformTable();
- }
-
- /**
- * Get seperator transform table required for converting
- * the . and , sign to appropriate forms in site content language.
- *
- * @return array
- */
- protected function getSeparatorTransformTable() {
- return $this->language->separatorTransformTable();
- }
/**
* Get all the dynamic data for the content language to an array.
*
- * NOTE: Before calling this you HAVE to make sure $this->language is set.
- *
+ * @param ResourceLoaderContext $context
* @return array
*/
- protected function getData() {
+ protected function getData( ResourceLoaderContext $context ) {
+ $language = Language::factory( $context->getLanguage() );
return array(
- 'digitTransformTable' => $this->getDigitTransformTable(),
- 'separatorTransformTable' => $this->getSeparatorTransformTable(),
- 'grammarForms' => $this->getSiteLangGrammarForms(),
- 'pluralRules' => $this->getPluralRules(),
- 'digitGroupingPattern' => $this->getDigitGroupingPattern(),
+ 'digitTransformTable' => $language->digitTransformTable(),
+ 'separatorTransformTable' => $language->separatorTransformTable(),
+ 'grammarForms' => $language->getGrammarForms(),
+ 'pluralRules' => $language->getPluralRules(),
+ 'digitGroupingPattern' => $language->digitGroupingPattern(),
+ 'fallbackLanguages' => $language->getFallbackLanguages(),
);
}
/**
- * @param $context ResourceLoaderContext
- * @return string: JavaScript code
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- $this->language = Language::factory( $context->getLanguage() );
return Xml::encodeJsCall( 'mw.language.setData', array(
- $this->language->getCode(),
- $this->getData()
+ $context->getLanguage(),
+ $this->getData( $context )
) );
}
/**
- * @param $context ResourceLoaderContext
- * @return int: UNIX timestamp
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
return max( 1, $this->getHashMtime( $context ) );
}
/**
- * @param $context ResourceLoaderContext
- * @return string: Hash
+ * @param ResourceLoaderContext $context
+ * @return string Hash
*/
public function getModifiedHash( ResourceLoaderContext $context ) {
- $this->language = Language::factory( $context->getLanguage() );
-
- return md5( serialize( $this->getData() ) );
+ return md5( serialize( $this->getData( $context ) ) );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderLanguageNamesModule.php b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
new file mode 100644
index 00000000..fe0c8454
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Resource loader module for providing language names.
+ *
+ * By default these names will be autonyms however other extensions may
+ * provided language names in the context language (e.g. cldr extension)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Ed Sanders
+ * @author Trevor Parscal
+ */
+
+/**
+ * ResourceLoader module for populating language specific data.
+ */
+class ResourceLoaderLanguageNamesModule extends ResourceLoaderModule {
+
+ protected $targets = array( 'desktop', 'mobile' );
+
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ protected function getData( ResourceLoaderContext $context ) {
+ return Language::fetchLanguageNames(
+ $context->getLanguage(),
+ 'all'
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall( 'mw.language.setData', array(
+ $context->getLanguage(),
+ 'languageNames',
+ $this->getData( $context )
+ ) );
+ }
+
+ public function getDependencies() {
+ return array( 'mediawiki.language.init' );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return max( 1, $this->getHashMtime( $context ) );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5( serialize( $this->getData( $context ) ) );
+ }
+
+}
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 11264fc8..45eb70f8 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -26,7 +26,6 @@
* Abstraction for resource loader modules, with name registration and maxage functionality.
*/
abstract class ResourceLoaderModule {
-
# Type of resource
const TYPE_SCRIPTS = 'scripts';
const TYPE_STYLES = 'styles';
@@ -65,13 +64,18 @@ abstract class ResourceLoaderModule {
// In-object cache for message blob mtime
protected $msgBlobMtime = array();
+ /**
+ * @var Config
+ */
+ protected $config;
+
/* Methods */
/**
* Get this module's name. This is set when the module is registered
* with ResourceLoader::register()
*
- * @return mixed: Name (string) or null if no name was set
+ * @return string|null Name (string) or null if no name was set
*/
public function getName() {
return $this->name;
@@ -91,7 +95,7 @@ abstract class ResourceLoaderModule {
* Get this module's origin. This is set when the module is registered
* with ResourceLoader::register()
*
- * @return int: ResourceLoaderModule class constant, the subclass default
+ * @return int ResourceLoaderModule class constant, the subclass default
* if not set manually
*/
public function getOrigin() {
@@ -102,7 +106,7 @@ abstract class ResourceLoaderModule {
* Set this module's origin. This is called by ResourceLoader::register()
* when registering the module. Other code should not call this.
*
- * @param int $origin origin
+ * @param int $origin Origin
*/
public function setOrigin( $origin ) {
$this->origin = $origin;
@@ -123,7 +127,7 @@ abstract class ResourceLoaderModule {
* Includes all relevant JS except loader scripts.
*
* @param ResourceLoaderContext $context
- * @return string: JavaScript code
+ * @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
// Stub, override expected
@@ -131,6 +135,27 @@ abstract class ResourceLoaderModule {
}
/**
+ * @return Config
+ * @since 1.24
+ */
+ public function getConfig() {
+ if ( $this->config === null ) {
+ // Ugh, fall back to default
+ $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * @param Config $config
+ * @since 1.24
+ */
+ public function setConfig( Config $config ) {
+ $this->config = $config;
+ }
+
+ /**
* Get the URL or URLs to load for this module's JS in debug mode.
* The default behavior is to return a load.php?only=scripts URL for
* the module, but file-based modules will want to override this to
@@ -142,20 +167,20 @@ abstract class ResourceLoaderModule {
* MUST return either an only= URL or a non-load.php URL.
*
* @param ResourceLoaderContext $context
- * @return array: Array of URLs
+ * @return array Array of URLs
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
- $url = ResourceLoader::makeLoaderURL(
- array( $this->getName() ),
- $context->getLanguage(),
- $context->getSkin(),
- $context->getUser(),
- $context->getVersion(),
- true, // debug
- 'scripts', // only
- $context->getRequest()->getBool( 'printable' ),
- $context->getRequest()->getBool( 'handheld' )
+ $resourceLoader = $context->getResourceLoader();
+ $derivative = new DerivativeResourceLoaderContext( $context );
+ $derivative->setModules( array( $this->getName() ) );
+ $derivative->setOnly( 'scripts' );
+ $derivative->setDebug( true );
+
+ $url = $resourceLoader->createLoaderURL(
+ $this->getSource(),
+ $derivative
);
+
return array( $url );
}
@@ -173,7 +198,7 @@ abstract class ResourceLoaderModule {
* Get all CSS for this module for a given skin.
*
* @param ResourceLoaderContext $context
- * @return array: List of CSS strings or array of CSS strings keyed by media type.
+ * @return array List of CSS strings or array of CSS strings keyed by media type.
* like array( 'screen' => '.foo { width: 0 }' );
* or array( 'screen' => array( '.foo { width: 0 }' ) );
*/
@@ -189,20 +214,20 @@ abstract class ResourceLoaderModule {
* load the files directly. See also getScriptURLsForDebug()
*
* @param ResourceLoaderContext $context
- * @return array: array( mediaType => array( URL1, URL2, ... ), ... )
+ * @return array Array( mediaType => array( URL1, URL2, ... ), ... )
*/
public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
- $url = ResourceLoader::makeLoaderURL(
- array( $this->getName() ),
- $context->getLanguage(),
- $context->getSkin(),
- $context->getUser(),
- $context->getVersion(),
- true, // debug
- 'styles', // only
- $context->getRequest()->getBool( 'printable' ),
- $context->getRequest()->getBool( 'handheld' )
+ $resourceLoader = $context->getResourceLoader();
+ $derivative = new DerivativeResourceLoaderContext( $context );
+ $derivative->setModules( array( $this->getName() ) );
+ $derivative->setOnly( 'styles' );
+ $derivative->setDebug( true );
+
+ $url = $resourceLoader->createLoaderURL(
+ $this->getSource(),
+ $derivative
);
+
return array( 'all' => array( $url ) );
}
@@ -211,7 +236,7 @@ abstract class ResourceLoaderModule {
*
* To get a JSON blob with messages, use MessageBlobStore::get()
*
- * @return array: List of message keys. Keys may occur more than once
+ * @return array List of message keys. Keys may occur more than once
*/
public function getMessages() {
// Stub, override expected
@@ -221,7 +246,7 @@ abstract class ResourceLoaderModule {
/**
* Get the group this module is in.
*
- * @return string: Group name
+ * @return string Group name
*/
public function getGroup() {
// Stub, override expected
@@ -231,7 +256,7 @@ abstract class ResourceLoaderModule {
/**
* Get the origin of this module. Should only be overridden for foreign modules.
*
- * @return string: Origin name, 'local' for local modules
+ * @return string Origin name, 'local' for local modules
*/
public function getSource() {
// Stub, override expected
@@ -263,7 +288,7 @@ abstract class ResourceLoaderModule {
/**
* Get the loader JS for this module, if set.
*
- * @return mixed: JavaScript loader code as a string or boolean false if no custom loader set
+ * @return mixed JavaScript loader code as a string or boolean false if no custom loader set
*/
public function getLoaderScript() {
// Stub, override expected
@@ -278,7 +303,7 @@ abstract class ResourceLoaderModule {
*
* To add dependencies dynamically on the client side, use a custom
* loader script, see getLoaderScript()
- * @return array: List of module names as strings
+ * @return array List of module names as strings
*/
public function getDependencies() {
// Stub, override expected
@@ -288,18 +313,36 @@ abstract class ResourceLoaderModule {
/**
* Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile']
*
- * @return array: Array of strings
+ * @return array Array of strings
*/
public function getTargets() {
return $this->targets;
}
/**
+ * Get the skip function.
+ *
+ * Modules that provide fallback functionality can provide a "skip function". This
+ * function, if provided, will be passed along to the module registry on the client.
+ * When this module is loaded (either directly or as a dependency of another module),
+ * then this function is executed first. If the function returns true, the module will
+ * instantly be considered "ready" without requesting the associated module resources.
+ *
+ * The value returned here must be valid javascript for execution in a private function.
+ * It must not contain the "function () {" and "}" wrapper though.
+ *
+ * @return string|null A JavaScript function body returning a boolean value, or null
+ */
+ public function getSkipFunction() {
+ return null;
+ }
+
+ /**
* Get the files this module depends on indirectly for a given skin.
* Currently these are only image files referenced by the module's CSS.
*
* @param string $skin Skin name
- * @return array: List of files
+ * @return array List of files
*/
public function getFileDependencies( $skin ) {
// Try in-object cache first
@@ -335,7 +378,7 @@ abstract class ResourceLoaderModule {
* Get the last modification timestamp of the message blob for this
* module in a given language.
* @param string $lang Language code
- * @return int: UNIX timestamp, or 0 if the module doesn't have messages
+ * @return int UNIX timestamp, or 0 if the module doesn't have messages
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
@@ -363,7 +406,7 @@ abstract class ResourceLoaderModule {
* Set a preloaded message blob last modification timestamp. Used so we
* can load this information for all modules at once.
* @param string $lang Language code
- * @param $mtime Integer: UNIX timestamp or 0 if there is no such blob
+ * @param int $mtime UNIX timestamp or 0 if there is no such blob
*/
public function setMsgBlobMtime( $lang, $mtime ) {
$this->msgBlobMtime[$lang] = $mtime;
@@ -387,7 +430,7 @@ abstract class ResourceLoaderModule {
* yourself and take its result into consideration.
*
* @param ResourceLoaderContext $context Context object
- * @return integer UNIX timestamp
+ * @return int UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
// 0 would mean now
@@ -398,7 +441,8 @@ abstract class ResourceLoaderModule {
* Helper method for calculating when the module's hash (if it has one) changed.
*
* @param ResourceLoaderContext $context
- * @return integer: UNIX timestamp or 0 if there is no hash provided
+ * @return int UNIX timestamp or 0 if no hash was provided
+ * by getModifiedHash()
*/
public function getHashMtime( ResourceLoaderContext $context ) {
$hash = $this->getModifiedHash( $context );
@@ -407,7 +451,7 @@ abstract class ResourceLoaderModule {
}
$cache = wfGetCache( CACHE_ANYTHING );
- $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() );
+ $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash );
$data = $cache->get( $key );
if ( is_array( $data ) && $data['hash'] === $hash ) {
@@ -425,17 +469,101 @@ abstract class ResourceLoaderModule {
}
/**
- * Get the last modification timestamp of the message blob for this
- * module in a given language.
+ * Get the hash for whatever this module may contain.
+ *
+ * This is the method subclasses should implement if they want to make
+ * use of getHashMTime() inside getModifiedTime().
*
* @param ResourceLoaderContext $context
- * @return string|null: Hash
+ * @return string|null Hash
*/
public function getModifiedHash( ResourceLoaderContext $context ) {
return null;
}
/**
+ * Helper method for calculating when this module's definition summary was last changed.
+ *
+ * @since 1.23
+ *
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp or 0 if no definition summary was provided
+ * by getDefinitionSummary()
+ */
+ public function getDefinitionMtime( ResourceLoaderContext $context ) {
+ wfProfileIn( __METHOD__ );
+ $summary = $this->getDefinitionSummary( $context );
+ if ( $summary === null ) {
+ wfProfileOut( __METHOD__ );
+ return 0;
+ }
+
+ $hash = md5( json_encode( $summary ) );
+
+ $cache = wfGetCache( CACHE_ANYTHING );
+
+ // Embed the hash itself in the cache key. This allows for a few nifty things:
+ // - During deployment, servers with old and new versions of the code communicating
+ // with the same memcached will not override the same key repeatedly increasing
+ // the timestamp.
+ // - In case of the definition changing and then changing back in a short period of time
+ // (e.g. in case of a revert or a corrupt server) the old timestamp and client-side cache
+ // url will be re-used.
+ // - If different context-combinations (e.g. same skin, same language or some combination
+ // thereof) result in the same definition, they will use the same hash and timestamp.
+ $key = wfMemcKey( 'resourceloader', 'moduledefinition', $this->getName(), $hash );
+
+ $data = $cache->get( $key );
+ if ( is_int( $data ) && $data > 0 ) {
+ // We've seen this hash before, re-use the timestamp of when we first saw it.
+ wfProfileOut( __METHOD__ );
+ return $data;
+ }
+
+ wfDebugLog( 'resourceloader', __METHOD__ . ": New definition hash for module "
+ . "{$this->getName()} in context {$context->getHash()}: $hash." );
+
+ $timestamp = time();
+ $cache->set( $key, $timestamp );
+
+ wfProfileOut( __METHOD__ );
+ return $timestamp;
+ }
+
+ /**
+ * Get the definition summary for this module.
+ *
+ * This is the method subclasses should implement if they want to make
+ * use of getDefinitionMTime() inside getModifiedTime().
+ *
+ * Return an array containing values from all significant properties of this
+ * module's definition. Be sure to include things that are explicitly ordered,
+ * in their actaul order (bug 37812).
+ *
+ * Avoid including things that are insiginificant (e.g. order of message
+ * keys is insignificant and should be sorted to avoid unnecessary cache
+ * invalidation).
+ *
+ * Avoid including things already considered by other methods inside your
+ * getModifiedTime(), such as file mtime timestamps.
+ *
+ * Serialisation is done using json_encode, which means object state is not
+ * taken into account when building the hash. This data structure must only
+ * contain arrays and scalars as values (avoid object instances) which means
+ * it requires abstraction.
+ *
+ * @since 1.23
+ *
+ * @param ResourceLoaderContext $context
+ * @return array|null
+ */
+ public function getDefinitionSummary( ResourceLoaderContext $context ) {
+ return array(
+ 'class' => get_class( $this ),
+ );
+ }
+
+ /**
* Check whether this module is known to be empty. If a child class
* has an easy and cheap way to determine that this module is
* definitely going to be empty, it should override this method to
@@ -448,7 +576,7 @@ abstract class ResourceLoaderModule {
return false;
}
- /** @var JSParser lazy-initialized; use self::javaScriptParser() */
+ /** @var JSParser Lazy-initialized; use self::javaScriptParser() */
private static $jsParser;
private static $parseCacheVersion = 1;
@@ -458,11 +586,10 @@ abstract class ResourceLoaderModule {
*
* @param string $fileName
* @param string $contents
- * @return string: JS with the original, or a replacement error
+ * @return string JS with the original, or a replacement error
*/
protected function validateScriptFile( $fileName, $contents ) {
- global $wgResourceLoaderValidateJS;
- if ( $wgResourceLoaderValidateJS ) {
+ if ( $this->getConfig()->get( 'ResourceLoaderValidateJS' ) ) {
// Try for cache hit
// Use CACHE_ANYTHING since filtering is very slow compared to DB queries
$key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) );
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderNoscriptModule.php
index bd026f3f..61927d77 100644
--- a/includes/resourceloader/ResourceLoaderNoscriptModule.php
+++ b/includes/resourceloader/ResourceLoaderNoscriptModule.php
@@ -33,9 +33,9 @@ class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
* Gets list of pages used by this module. Obviously, it makes absolutely no
* sense to include JavaScript files here... :D
*
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
*
- * @return Array: List of pages
+ * @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
return array( 'MediaWiki:Noscript.css' => array( 'type' => 'style' ) );
@@ -46,7 +46,7 @@ class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
/**
* Gets group name
*
- * @return String: Name of group
+ * @return string Name of group
*/
public function getGroup() {
return 'noscript';
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 05754d37..1d9721aa 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -32,19 +32,17 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets list of pages used by this module
*
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
*
- * @return Array: List of pages
+ * @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUseSiteJs, $wgUseSiteCss;
-
$pages = array();
- if ( $wgUseSiteJs ) {
+ if ( $this->getConfig()->get( 'UseSiteJs' ) ) {
$pages['MediaWiki:Common.js'] = array( 'type' => 'script' );
$pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.js'] = array( 'type' => 'script' );
}
- if ( $wgUseSiteCss ) {
+ if ( $this->getConfig()->get( 'UseSiteCss' ) ) {
$pages['MediaWiki:Common.css'] = array( 'type' => 'style' );
$pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'] = array( 'type' => 'style' );
@@ -58,7 +56,7 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets group name
*
- * @return String: Name of group
+ * @return string Name of group
*/
public function getGroup() {
return 'site';
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 20f6e0ba..78fe8e01 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -27,21 +27,23 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
/* Protected Members */
protected $modifiedTime = array();
+ protected $configVars = array();
protected $targets = array( 'desktop', 'mobile' );
/* Protected Methods */
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
- protected function getConfig( $context ) {
- global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
- $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
- $wgVariantArticlePath, $wgActionPaths, $wgVersion,
- $wgEnableAPI, $wgEnableWriteAPI, $wgDBname,
- $wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
- $wgCookiePrefix, $wgResourceLoaderMaxQueryLength;
+ protected function getConfigSettings( $context ) {
+
+ $hash = $context->getHash();
+ if ( isset( $this->configVars[$hash] ) ) {
+ return $this->configVars[$hash];
+ }
+
+ global $wgContLang;
$mainPage = Title::newMainPage();
@@ -59,113 +61,262 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
}
+ $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,
- '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,
+ '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,
- 'wgSiteName' => $wgSitename,
- 'wgFileExtensions' => array_values( array_unique( $wgFileExtensions ) ),
- 'wgDBname' => $wgDBname,
+ 'wgContentNamespaces' => MWNamespace::getContentNamespaces(),
+ '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(),
+ 'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
'wgAvailableSkins' => Skin::getSkinNames(),
- 'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
+ 'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
// MediaWiki sets cookies to have this prefix by default
- 'wgCookiePrefix' => $wgCookiePrefix,
- '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' => $conf->get( 'ResourceLoaderStorageVersion' ),
+ 'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
);
wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
- return $vars;
+ $this->configVars[$hash] = $vars;
+ return $this->configVars[$hash];
+ }
+
+ /**
+ * Recursively get all explicit and implicit dependencies for to the given module.
+ *
+ * @param array $registryData
+ * @param string $moduleName
+ * @return array
+ */
+ protected static function getImplicitDependencies( array $registryData, $moduleName ) {
+ static $dependencyCache = array();
+
+ // The list of implicit dependencies won't be altered, so we can
+ // cache them without having to worry.
+ if ( !isset( $dependencyCache[$moduleName] ) ) {
+
+ if ( !isset( $registryData[$moduleName] ) ) {
+ // Dependencies may not exist
+ $dependencyCache[$moduleName] = array();
+ } else {
+ $data = $registryData[$moduleName];
+ $dependencyCache[$moduleName] = $data['dependencies'];
+
+ foreach ( $data['dependencies'] as $dependency ) {
+ // Recursively get the dependencies of the dependencies
+ $dependencyCache[$moduleName] = array_merge(
+ $dependencyCache[$moduleName],
+ self::getImplicitDependencies( $registryData, $dependency )
+ );
+ }
+ }
+ }
+
+ return $dependencyCache[$moduleName];
+ }
+
+ /**
+ * Optimize the dependency tree in $this->modules and return it.
+ *
+ * The optimization basically works like this:
+ * Given we have module A with the dependencies B and C
+ * and module B with the dependency C.
+ * Now we don't have to tell the client to explicitly fetch module
+ * C as that's already included in module B.
+ *
+ * This way we can reasonably reduce the amout of module registration
+ * data send to the client.
+ *
+ * @param array &$registryData Modules keyed by name with properties:
+ * - string 'version'
+ * - array 'dependencies'
+ * - string|null 'group'
+ * - string 'source'
+ * - string|false 'loader'
+ */
+ public static function compileUnresolvedDependencies( array &$registryData ) {
+ foreach ( $registryData as $name => &$data ) {
+ if ( $data['loader'] !== false ) {
+ continue;
+ }
+ $dependencies = $data['dependencies'];
+ foreach ( $data['dependencies'] as $dependency ) {
+ $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
+ $dependencies = array_diff( $dependencies, $implicitDependencies );
+ }
+ // Rebuild keys
+ $data['dependencies'] = array_values( $dependencies );
+ }
}
+
/**
- * Gets registration code for all modules
+ * Get registration code for all modules.
*
- * @param $context ResourceLoaderContext object
- * @return String: JavaScript code for registering all modules with the client loader
+ * @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__ );
- $out = '';
- $registrations = array();
$resourceLoader = $context->getResourceLoader();
$target = $context->getRequest()->getVal( 'target', 'desktop' );
- // Register sources
- $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
+ $out = '';
+ $registryData = array();
- // Register modules
+ // Get registry data
foreach ( $resourceLoader->getModuleNames() as $name ) {
$module = $resourceLoader->getModule( $name );
$moduleTargets = $module->getTargets();
if ( !in_array( $target, $moduleTargets ) ) {
continue;
}
- $deps = $module->getDependencies();
- $group = $module->getGroup();
- $source = $module->getSource();
- // Support module loader scripts
- $loader = $module->getLoaderScript();
- if ( $loader !== false ) {
- $version = wfTimestamp( TS_ISO_8601_BASIC,
- $module->getModifiedTime( $context ) );
- $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $source, $loader );
+
+ if ( $module->isRaw() ) {
+ // Don't register "raw" modules (like 'jquery' and 'mediawiki') client-side because
+ // depending on them is illegal anyway and would only lead to them being reloaded
+ // causing any state to be lost (like jQuery plugins, mw.config etc.)
continue;
}
- // Automatically register module
// 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 ) );
- // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to
- // mw.loader.register()
- if ( !count( $deps ) && $group === null && $source === 'local' ) {
- $registrations[] = array( $name, $mtime );
- }
- // Modules with dependencies but no group or foreign source pass three arguments
- // (name, timestamp, dependencies) to mw.loader.register()
- elseif ( $group === null && $source === 'local' ) {
- $registrations[] = array( $name, $mtime, $deps );
+ $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ) );
+
+ // FIXME: Convert to numbers, wfTimestamp always gives us stings, even for TS_UNIX
+
+ $skipFunction = $module->getSkipFunction();
+ if ( $skipFunction !== null && !ResourceLoader::inDebugMode() ) {
+ $skipFunction = $resourceLoader->filter( 'minify-js',
+ $skipFunction,
+ // There will potentially be lots of these little string in the registrations
+ // manifest, we don't want to blow up the startup module with
+ // "/* cache key: ... */" all over it in non-debug mode.
+ /* cacheReport = */ false
+ );
}
- // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group)
- // to mw.loader.register()
- elseif ( $source === 'local' ) {
- $registrations[] = array( $name, $mtime, $deps, $group );
+
+ $registryData[$name] = array(
+ 'version' => $mtime,
+ 'dependencies' => $module->getDependencies(),
+ 'group' => $module->getGroup(),
+ 'source' => $module->getSource(),
+ 'loader' => $module->getLoaderScript(),
+ 'skip' => $skipFunction,
+ );
+ }
+
+ self::compileUnresolvedDependencies( $registryData );
+
+ // Register sources
+ $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
+
+ // Concatenate module loader scripts and figure out the different call
+ // signatures for mw.loader.register
+ $registrations = array();
+ foreach ( $registryData as $name => $data ) {
+ if ( $data['loader'] !== false ) {
+ $out .= ResourceLoader::makeCustomLoaderScript(
+ $name,
+ wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ),
+ $data['dependencies'],
+ $data['group'],
+ $data['source'],
+ $data['loader']
+ );
+ continue;
}
- // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source)
- // to mw.loader.register()
- else {
- $registrations[] = array( $name, $mtime, $deps, $group, $source );
+
+ if (
+ !count( $data['dependencies'] ) &&
+ $data['group'] === null &&
+ $data['source'] === 'local' &&
+ $data['skip'] === null
+ ) {
+ // Modules with no dependencies, group, foreign source or skip function;
+ // call mw.loader.register(name, timestamp)
+ $registrations[] = array( $name, $data['version'] );
+ } elseif (
+ $data['group'] === null &&
+ $data['source'] === 'local' &&
+ $data['skip'] === null
+ ) {
+ // Modules with dependencies but no group, foreign source or skip function;
+ // call mw.loader.register(name, timestamp, dependencies)
+ $registrations[] = array( $name, $data['version'], $data['dependencies'] );
+ } elseif (
+ $data['source'] === 'local' &&
+ $data['skip'] === null
+ ) {
+ // Modules with a group but no foreign source or skip function;
+ // call mw.loader.register(name, timestamp, dependencies, group)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group']
+ );
+ } elseif ( $data['skip'] === null ) {
+ // Modules with a foreign source but no skip function;
+ // call mw.loader.register(name, timestamp, dependencies, group, source)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group'],
+ $data['source']
+ );
+ } else {
+ // Modules with a skip function;
+ // call mw.loader.register(name, timestamp, dependencies, group, source, skip)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group'],
+ $data['source'],
+ $data['skip']
+ );
}
}
+
+ // Register modules
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
wfProfileOut( __METHOD__ );
@@ -182,55 +333,75 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * @param $context ResourceLoaderContext
+ * 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.
+ *
+ * This is a helper for getScript(), but can also be called standalone, such
+ * as when generating an AppCache manifest.
+ *
+ * @param ResourceLoaderContext $context
* @return string
*/
- public function getScript( ResourceLoaderContext $context ) {
- global $IP, $wgLegacyJavaScriptGlobals;
+ public static function getStartupModulesUrl( ResourceLoaderContext $context ) {
+ $moduleNames = self::getStartupModules();
- $out = file_get_contents( "$IP/resources/startup.js" );
- if ( $context->getOnly() === 'scripts' ) {
+ // Get the latest version
+ $loader = $context->getResourceLoader();
+ $version = 0;
+ foreach ( $moduleNames as $moduleName ) {
+ $version = max( $version,
+ $loader->getModule( $moduleName )->getModifiedTime( $context )
+ );
+ }
+
+ $query = array(
+ 'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
+ 'only' => 'scripts',
+ 'lang' => $context->getLanguage(),
+ 'skin' => $context->getSkin(),
+ 'debug' => $context->getDebug() ? 'true' : 'false',
+ 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
+ );
+ // Ensure uniform query order
+ ksort( $query );
+ return wfAppendQuery( wfScript( 'load' ), $query );
+ }
- // The core modules:
- $moduleNames = array( 'jquery', 'mediawiki' );
- wfRunHooks( 'ResourceLoaderGetStartupModules', array( &$moduleNames ) );
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ global $IP;
- // Get the latest version
- $loader = $context->getResourceLoader();
- $version = 0;
- foreach ( $moduleNames as $moduleName ) {
- $version = max( $version,
- $loader->getModule( $moduleName )->getModifiedTime( $context )
- );
- }
- // Build load query for StartupModules
- $query = array(
- 'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
- 'only' => 'scripts',
- 'lang' => $context->getLanguage(),
- 'skin' => $context->getSkin(),
- 'debug' => $context->getDebug() ? 'true' : 'false',
- 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
- );
- // Ensure uniform query order
- ksort( $query );
+ $out = file_get_contents( "$IP/resources/src/startup.js" );
+ if ( $context->getOnly() === 'scripts' ) {
// Startup function
- $configuration = $this->getConfig( $context );
- $registrations = self::getModuleRegistrations( $context );
- $registrations = str_replace( "\n", "\n\t", trim( $registrations ) ); // fix indentation
- $out .= "var startUp = function() {\n" .
- "\tmw.config = new " . Xml::encodeJsCall( 'mw.Map', array( $wgLegacyJavaScriptGlobals ) ) . "\n" .
+ $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( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" .
"\t$registrations\n" .
"\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
"};\n";
// Conditional script injection
- $scriptTag = Html::linkedScript( wfAppendQuery( wfScript( 'load' ), $query ) );
+ $scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) );
$out .= "if ( isCompatible() ) {\n" .
"\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
- "}\n" .
- "delete isCompatible;";
+ "}";
}
return $out;
@@ -244,11 +415,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- global $IP, $wgCacheEpoch;
+ global $IP;
$hash = $context->getHash();
if ( isset( $this->modifiedTime[$hash] ) ) {
@@ -260,19 +431,44 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$loader = $context->getResourceLoader();
$loader->preloadModuleInfo( $loader->getModuleNames(), $context );
- $this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
- // ATTENTION!: Because of the line above, this is not going to cause
+ $time = max(
+ wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) ),
+ filemtime( "$IP/resources/src/startup.js" ),
+ $this->getHashMtime( $context )
+ );
+
+ // ATTENTION!: Because of the line below, this is not going to cause
// infinite recursion - think carefully before making changes to this
// code!
- $time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
+ // Pre-populate modifiedTime with something because the the loop over
+ // all modules below includes the the startup module (this module).
+ $this->modifiedTime[$hash] = 1;
+
foreach ( $loader->getModuleNames() as $name ) {
$module = $loader->getModule( $name );
$time = max( $time, $module->getModifiedTime( $context ) );
}
- return $this->modifiedTime[$hash] = $time;
+
+ $this->modifiedTime[$hash] = $time;
+ return $this->modifiedTime[$hash];
}
- /* Methods */
+ /**
+ * Hash of all dynamic data embedded in getScript().
+ *
+ * Detect changes to mw.config settings embedded in #getScript (bug 28899).
+ *
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ $data = array(
+ 'vars' => $this->getConfigSettings( $context ),
+ 'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
+ );
+
+ return md5( serialize( $data ) );
+ }
/**
* @return string
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
index bda86539..40274c63 100644
--- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -36,27 +36,27 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
/* Methods */
/**
- * @param $context ResourceLoaderContext
- * @return array|int|Mixed
+ * @param ResourceLoaderContext $context
+ * @return array|int|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
- if ( isset( $this->modifiedTime[$hash] ) ) {
- return $this->modifiedTime[$hash];
+ if ( !isset( $this->modifiedTime[$hash] ) ) {
+ global $wgUser;
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
- global $wgUser;
- return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ return $this->modifiedTime[$hash];
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgAllowUserCssPrefs, $wgUser;
+ global $wgUser;
- if ( !$wgAllowUserCssPrefs ) {
+ if ( !$this->getConfig()->get( 'AllowUserCssPrefs' ) ) {
return array();
}
@@ -71,17 +71,8 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
( $options['underline'] ? 'underline' : 'none' ) . "; }";
} else {
# The scripts of these languages are very hard to read with underlines
- $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(kk-arab), ' .
- 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }';
- }
- if ( $options['justify'] ) {
- $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
- }
- if ( !$options['showtoc'] ) {
- $rules[] = "#toc { display: none; }\n";
- }
- if ( !$options['editsection'] ) {
- $rules[] = ".mw-editsection { display: none; }\n";
+ $rules[] = 'a:lang(ar), a:lang(kk-arab), a:lang(mzn), ' .
+ 'a:lang(ps), a:lang(ur) { text-decoration: none; }';
}
if ( $options['editfont'] !== 'default' ) {
// Double-check that $options['editfont'] consists of safe characters only
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
index 9064263f..7cf19420 100644
--- a/includes/resourceloader/ResourceLoaderUserGroupsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -25,21 +25,28 @@
*/
class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
- /* Protected Methods */
+ /* Protected Members */
+
protected $origin = self::ORIGIN_USER_SITEWIDE;
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /* Protected Methods */
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUser, $wgUseSiteJs, $wgUseSiteCss;
+ global $wgUser;
$userName = $context->getUser();
if ( $userName === null ) {
return array();
}
- if ( !$wgUseSiteJs && !$wgUseSiteCss ) {
+
+ $useSiteJs = $this->getConfig()->get( 'UseSiteJs' );
+ $useSiteCss = $this->getConfig()->get( 'UseSiteCss' );
+ if ( !$useSiteJs && !$useSiteCss ) {
return array();
}
@@ -55,13 +62,13 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
$pages = array();
foreach ( $user->getEffectiveGroups() as $group ) {
- if ( in_array( $group, array( '*', 'user' ) ) ) {
+ if ( $group == '*' ) {
continue;
}
- if ( $wgUseSiteJs ) {
+ if ( $useSiteJs ) {
$pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' );
}
- if ( $wgUseSiteCss ) {
+ if ( $useSiteCss ) {
$pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' );
}
}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index 7a04e473..1b6d1de0 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -27,21 +27,27 @@
*/
class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
- /* Protected Methods */
+ /* Protected Members */
+
protected $origin = self::ORIGIN_USER_INDIVIDUAL;
+ /* Protected Methods */
+
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgAllowUserJs, $wgAllowUserCss;
$username = $context->getUser();
if ( $username === null ) {
return array();
}
- if ( !$wgAllowUserJs && !$wgAllowUserCss ) {
+
+ $allowUserJs = $this->getConfig()->get( 'AllowUserJs' );
+ $allowUserCss = $this->getConfig()->get( 'AllowUserCss' );
+
+ if ( !$allowUserJs && !$allowUserCss ) {
return array();
}
@@ -55,11 +61,11 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
$userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works
$pages = array();
- if ( $wgAllowUserJs ) {
+ if ( $allowUserJs ) {
$pages["$userpage/common.js"] = array( 'type' => 'script' );
$pages["$userpage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
}
- if ( $wgAllowUserCss ) {
+ if ( $allowUserCss ) {
$pages["$userpage/common.css"] = array( 'type' => 'style' );
$pages["$userpage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
}
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 0b7e1964..bd97a8e5 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -33,24 +33,26 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+ protected $targets = array( 'desktop', 'mobile' );
+
/* Methods */
/**
- * @param $context ResourceLoaderContext
- * @return array|int|Mixed
+ * @param ResourceLoaderContext $context
+ * @return array|int|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
- if ( isset( $this->modifiedTime[$hash] ) ) {
- return $this->modifiedTime[$hash];
+ if ( !isset( $this->modifiedTime[$hash] ) ) {
+ global $wgUser;
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
- global $wgUser;
- return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ return $this->modifiedTime[$hash];
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 92ebbe93..668467ca 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -30,25 +30,27 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+ protected $targets = array( 'desktop', 'mobile' );
+
/* Methods */
/**
* Fetch the tokens for the current user.
*
- * @return array: List of tokens keyed by token type
+ * @return array List of tokens keyed by token type
*/
protected function contextUserTokens() {
global $wgUser;
return array(
'editToken' => $wgUser->getEditToken(),
- 'patrolToken' => ApiQueryRecentChanges::getPatrolToken( null, null ),
- 'watchToken' => ApiQueryInfo::getWatchToken( null, null ),
+ 'patrolToken' => $wgUser->getEditToken( 'patrol' ),
+ 'watchToken' => $wgUser->getEditToken( 'watch' ),
);
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index 3f10ae53..de61fc55 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -36,8 +36,8 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
# Origin is user-supplied code
protected $origin = self::ORIGIN_USER_SITEWIDE;
- // In-object cache for title mtimes
- protected $titleMtimes = array();
+ // In-object cache for title info
+ protected $titleInfo = array();
/* Abstract Protected Methods */
@@ -54,7 +54,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* There is an optional media key, the value of which can be the
* medium ('screen', 'print', etc.) of the stylesheet.
*
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
abstract protected function getPages( ResourceLoaderContext $context );
@@ -77,7 +77,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
/**
- * @param $title Title
+ * @param Title $title
* @return null|string
*/
protected function getContent( $title ) {
@@ -96,20 +96,20 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return null;
}
- $model = $content->getModel();
-
- if ( $model !== CONTENT_MODEL_CSS && $model !== CONTENT_MODEL_JAVASCRIPT ) {
- wfDebugLog( 'resourceloader', __METHOD__ . ': bad content model $model for JS/CSS page!' );
+ if ( $content->isSupportedFormat( CONTENT_FORMAT_JAVASCRIPT ) ) {
+ return $content->serialize( CONTENT_FORMAT_JAVASCRIPT );
+ } elseif ( $content->isSupportedFormat( CONTENT_FORMAT_CSS ) ) {
+ return $content->serialize( CONTENT_FORMAT_CSS );
+ } else {
+ wfDebugLog( 'resourceloader', __METHOD__ . ": bad content model {$content->getModel()} for JS/CSS page!" );
return null;
}
-
- return $content->getNativeData(); //NOTE: this is safe, we know it's JS or CSS
}
/* Methods */
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
@@ -125,22 +125,17 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
$script = $this->getContent( $title );
if ( strval( $script ) !== '' ) {
$script = $this->validateScriptFile( $titleText, $script );
- if ( strpos( $titleText, '*/' ) === false ) {
- $scripts .= "/* $titleText */\n";
- }
- $scripts .= $script . "\n";
+ $scripts .= ResourceLoader::makeComment( $titleText ) . $script . "\n";
}
}
return $scripts;
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgScriptPath;
-
$styles = array();
foreach ( $this->getPages( $context ) as $titleText => $options ) {
if ( $options['type'] !== 'style' ) {
@@ -158,47 +153,84 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( $this->getFlip( $context ) ) {
$style = CSSJanus::transform( $style, true, false );
}
- $style = CSSMin::remap( $style, false, $wgScriptPath, true );
+ $style = CSSMin::remap( $style, false, $this->getConfig()->get( 'ScriptPath' ), true );
if ( !isset( $styles[$media] ) ) {
$styles[$media] = array();
}
- if ( strpos( $titleText, '*/' ) === false ) {
- $style = "/* $titleText */\n" . $style;
- }
+ $style = ResourceLoader::makeComment( $titleText ) . $style;
$styles[$media][] = $style;
}
return $styles;
}
/**
- * @param $context ResourceLoaderContext
+ * @param ResourceLoaderContext $context
* @return int|mixed
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
- $mtimes = $this->getTitleMtimes( $context );
- if ( count( $mtimes ) ) {
+ $titleInfo = $this->getTitleInfo( $context );
+ if ( count( $titleInfo ) ) {
+ $mtimes = array_map( function( $value ) {
+ return $value['timestamp'];
+ }, $titleInfo );
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
}
- $modifiedTime = max( $modifiedTime, $this->getMsgBlobMtime( $context->getLanguage() ) );
+ $modifiedTime = max(
+ $modifiedTime,
+ $this->getMsgBlobMtime( $context->getLanguage() ),
+ $this->getDefinitionMtime( $context )
+ );
return $modifiedTime;
}
/**
- * @param $context ResourceLoaderContext
+ * Get the definition summary for this module.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getDefinitionSummary( ResourceLoaderContext $context ) {
+ return array(
+ 'class' => get_class( $this ),
+ 'pages' => $this->getPages( $context ),
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
* @return bool
*/
public function isKnownEmpty( ResourceLoaderContext $context ) {
- return count( $this->getTitleMtimes( $context ) ) == 0;
+ $titleInfo = $this->getTitleInfo( $context );
+ // Bug 68488: For modules in the "user" group, we should actually
+ // check that the pages are empty (page_len == 0), but for other
+ // groups, just check the pages exist so that we don't end up
+ // caching temporarily-blank pages without the appropriate
+ // <script> or <link> tag.
+ if ( $this->getGroup() !== 'user' ) {
+ return count( $titleInfo ) === 0;
+ }
+
+ foreach ( $titleInfo as $info ) {
+ if ( $info['length'] !== 0 ) {
+ // At least one non-0-lenth page, not empty
+ return false;
+ }
+ }
+
+ // All pages are 0-length, so it's empty
+ return true;
}
/**
* Get the modification times of all titles that would be loaded for
* a given context.
- * @param $context ResourceLoaderContext: Context object
- * @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped
+ * @param ResourceLoaderContext $context Context object
+ * @return array keyed by page dbkey, with value is an array with 'length' and 'timestamp'
+ * keys, where the timestamp is a unix one
*/
- protected function getTitleMtimes( ResourceLoaderContext $context ) {
+ protected function getTitleInfo( ResourceLoaderContext $context ) {
$dbr = $this->getDB();
if ( !$dbr ) {
// We're dealing with a subclass that doesn't have a DB
@@ -206,11 +238,11 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
$hash = $context->getHash();
- if ( isset( $this->titleMtimes[$hash] ) ) {
- return $this->titleMtimes[$hash];
+ if ( isset( $this->titleInfo[$hash] ) ) {
+ return $this->titleInfo[$hash];
}
- $this->titleMtimes[$hash] = array();
+ $this->titleInfo[$hash] = array();
$batch = new LinkBatch;
foreach ( $this->getPages( $context ) as $titleText => $options ) {
$batch->addObj( Title::newFromText( $titleText ) );
@@ -218,16 +250,18 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( !$batch->isEmpty() ) {
$res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title', 'page_touched' ),
+ array( 'page_namespace', 'page_title', 'page_touched', 'page_len' ),
$batch->constructSet( 'page', $dbr ),
__METHOD__
);
foreach ( $res as $row ) {
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $this->titleMtimes[$hash][$title->getPrefixedDBkey()] =
- wfTimestamp( TS_UNIX, $row->page_touched );
+ $this->titleInfo[$hash][$title->getPrefixedDBkey()] = array(
+ 'timestamp' => wfTimestamp( TS_UNIX, $row->page_touched ),
+ 'length' => $row->page_len,
+ );
}
}
- return $this->titleMtimes[$hash];
+ return $this->titleInfo[$hash];
}
}