summaryrefslogtreecommitdiff
path: root/includes/resourceloader
diff options
context:
space:
mode:
Diffstat (limited to 'includes/resourceloader')
-rw-r--r--includes/resourceloader/ResourceLoader.php297
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php50
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php270
-rw-r--r--includes/resourceloader/ResourceLoaderFilePageModule.php11
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php189
-rw-r--r--includes/resourceloader/ResourceLoaderNoscriptModule.php52
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php4
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php143
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php59
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php12
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php29
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php63
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php44
13 files changed, 937 insertions, 286 deletions
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 191bc9f0..2a2e2981 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -29,7 +29,7 @@
class ResourceLoader {
/* Protected Static Members */
- protected static $filterCacheVersion = 2;
+ protected static $filterCacheVersion = 4;
/** Array: List of module name/ResourceLoaderModule object pairs */
protected $modules = array();
@@ -40,15 +40,15 @@ class ResourceLoader {
/**
* Loads information stored in the database about modules.
- *
- * This method grabs modules dependencies from the database and updates modules
+ *
+ * This method grabs modules dependencies from the database and updates modules
* objects.
- *
- * This is not inside the module code because it is much faster to
- * request all of the information at once than it is to have each module
+ *
+ * This is not inside the module code because it is much faster to
+ * request all of the information at once than it is to have each module
* requests its own information. This sacrifice of modularity yields a substantial
* performance improvement.
- *
+ *
* @param $modules Array: List of module names to preload information for
* @param $context ResourceLoaderContext: Context to load the information within
*/
@@ -59,11 +59,11 @@ class ResourceLoader {
$dbr = wfGetDB( DB_SLAVE );
$skin = $context->getSkin();
$lang = $context->getLanguage();
-
+
// Get file dependency information
$res = $dbr->select( 'module_deps', array( 'md_module', 'md_deps' ), array(
'md_module' => $modules,
- 'md_skin' => $context->getSkin()
+ 'md_skin' => $skin
), __METHOD__
);
@@ -80,7 +80,7 @@ class ResourceLoader {
foreach ( array_diff( $modules, $modulesWithDeps ) as $name ) {
$this->getModule( $name )->setFileDependencies( $skin, array() );
}
-
+
// Get message blob mtimes. Only do this for modules with messages
$modulesWithMessages = array();
foreach ( $modules as $name ) {
@@ -96,11 +96,11 @@ class ResourceLoader {
), __METHOD__
);
foreach ( $res as $row ) {
- $this->getModule( $row->mr_resource )->setMsgBlobMtime( $lang,
+ $this->getModule( $row->mr_resource )->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 );
}
@@ -108,14 +108,14 @@ class ResourceLoader {
/**
* Runs 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
- *
- * If $data is empty, only contains whitespace or the filter was unknown,
+ *
+ * If $data is empty, only contains whitespace or the filter was unknown,
* $data is returned unmodified.
- *
+ *
* @param $filter String: Name of filter to run
* @param $data String: Text to filter, such as JavaScript or CSS text
* @return String: Filtered data, or a comment containing an error message
@@ -124,10 +124,10 @@ class ResourceLoader {
global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
wfProfileIn( __METHOD__ );
- // For empty/whitespace-only data or for unknown filters, don't perform
+ // 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;
@@ -135,7 +135,7 @@ class ResourceLoader {
// Try for cache hit
// Use CACHE_ANYTHING since filtering is very slow compared to DB queries
- $key = wfMemcKey( 'resourceloader', 'filter', $filter, md5( $data ) );
+ $key = wfMemcKey( 'resourceloader', 'filter', $filter, self::$filterCacheVersion, md5( $data ) );
$cache = wfGetCache( CACHE_ANYTHING );
$cacheEntry = $cache->get( $key );
if ( is_string( $cacheEntry ) ) {
@@ -143,6 +143,7 @@ class ResourceLoader {
return $cacheEntry;
}
+ $result = '';
// Run the filter - we've already verified one of these will work
try {
switch ( $filter ) {
@@ -151,9 +152,11 @@ class ResourceLoader {
$wgResourceLoaderMinifierStatementsOnOwnLine,
$wgResourceLoaderMinifierMaxLineLength
);
+ $result .= "\n\n/* cache key: $key */\n";
break;
case 'minify-css':
$result = CSSMin::minify( $data );
+ $result .= "\n\n/* cache key: $key */\n";
break;
}
@@ -165,7 +168,7 @@ class ResourceLoader {
}
wfProfileOut( __METHOD__ );
-
+
return $result;
}
@@ -176,24 +179,24 @@ class ResourceLoader {
*/
public function __construct() {
global $IP, $wgResourceModules;
-
+
wfProfileIn( __METHOD__ );
-
+
// Register core modules
$this->register( include( "$IP/resources/Resources.php" ) );
// Register extension modules
wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
$this->register( $wgResourceModules );
-
+
wfProfileOut( __METHOD__ );
}
/**
* Registers 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 $info Module info array. For backwards compatibility with 1.17alpha,
- * this may also be a ResourceLoaderModule object. Optional when using
+ * @param $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)
@@ -217,14 +220,14 @@ class ResourceLoader {
if ( isset( $this->moduleInfos[$name] ) ) {
// A module has already been registered by this name
throw new MWException(
- 'ResourceLoader duplicate registration error. ' .
+ 'ResourceLoader duplicate registration error. ' .
'Another module has already been registered as ' . $name
);
}
-
+
// Check $name for illegal characters
- if ( preg_match( '/[|,]/', $name ) ) {
- throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|) or commas (,)" );
+ if ( preg_match( '/[|,!]/', $name ) ) {
+ throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|), commas (,) or exclamation marks (!)" );
}
// Attach module
@@ -232,7 +235,7 @@ class ResourceLoader {
// Old calling convention
// Validate the input
if ( !( $info instanceof ResourceLoaderModule ) ) {
- throw new MWException( 'ResourceLoader invalid module error. ' .
+ throw new MWException( 'ResourceLoader invalid module error. ' .
'Instances of ResourceLoaderModule expected.' );
}
@@ -260,7 +263,7 @@ class ResourceLoader {
* Get the ResourceLoaderModule object for a given module name.
*
* @param $name String: Module name
- * @return Mixed: ResourceLoaderModule if module has been registered, null otherwise
+ * @return ResourceLoaderModule if module has been registered, null otherwise
*/
public function getModule( $name ) {
if ( !isset( $this->modules[$name] ) ) {
@@ -295,7 +298,7 @@ class ResourceLoader {
*/
public function respond( ResourceLoaderContext $context ) {
global $wgResourceLoaderMaxage, $wgCacheEpoch;
-
+
// Buffer output to catch warnings. Normally we'd use ob_clean() on the
// top-level output buffer to clear warnings, but that breaks when ob_gzhandler
// is used: ob_clean() will clear the GZIP header in that case and it won't come
@@ -319,13 +322,13 @@ class ResourceLoader {
}
}
- // If a version wasn't specified we need a shorter expiry time for updates
+ // If a version wasn't specified we need a shorter expiry time for updates
// to propagate to clients quickly
if ( is_null( $context->getVersion() ) ) {
$maxage = $wgResourceLoaderMaxage['unversioned']['client'];
$smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
}
- // If a version was specified we can use a longer expiry time since changing
+ // If a version was specified we can use a longer expiry time since changing
// version numbers causes cache misses
else {
$maxage = $wgResourceLoaderMaxage['versioned']['client'];
@@ -343,7 +346,7 @@ class ResourceLoader {
wfProfileIn( __METHOD__.'-getModifiedTime' );
$private = false;
- // To send Last-Modified and support If-Modified-Since, we need to detect
+ // To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
$mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch );
foreach ( $modules as $module ) {
@@ -387,7 +390,8 @@ class ResourceLoader {
// Some clients send "timestamp;length=123". Strip the part after the first ';'
// so we get a valid timestamp.
$ims = $context->getRequest()->getHeader( 'If-Modified-Since' );
- if ( $ims !== false ) {
+ // Never send 304s in debug mode
+ if ( $ims !== false && !$context->getDebug() ) {
$imsTS = strtok( $ims, ';' );
if ( $mtime <= wfTimestamp( TS_UNIX, $imsTS ) ) {
// There's another bug in ob_gzhandler (see also the comment at
@@ -406,17 +410,17 @@ class ResourceLoader {
for ( $i = 0; $i < ob_get_level(); $i++ ) {
ob_end_clean();
}
-
+
header( 'HTTP/1.0 304 Not Modified' );
header( 'Status: 304 Not Modified' );
wfProfileOut( __METHOD__ );
return;
}
}
-
+
// Generate a response
$response = $this->makeModuleResponse( $context, $modules, $missing );
-
+
// Prepend comments indicating exceptions
$response = $exceptions . $response;
@@ -435,21 +439,21 @@ class ResourceLoader {
/**
* Generates code for a response
- *
+ *
* @param $context ResourceLoaderContext: Context in which to generate a response
* @param $modules Array: List of module objects keyed by module name
* @param $missing Array: List of unavailable modules (optional)
* @return String: Response data
*/
- public function makeModuleResponse( ResourceLoaderContext $context,
- array $modules, $missing = array() )
+ public function makeModuleResponse( ResourceLoaderContext $context,
+ array $modules, $missing = array() )
{
$out = '';
$exceptions = '';
if ( $modules === array() && $missing === array() ) {
return '/* No modules requested. Max made me put this here */';
}
-
+
wfProfileIn( __METHOD__ );
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
@@ -467,18 +471,33 @@ class ResourceLoader {
foreach ( $modules as $name => $module ) {
wfProfileIn( __METHOD__ . '-' . $name );
try {
- // Scripts
$scripts = '';
if ( $context->shouldIncludeScripts() ) {
- // bug 27054: Append semicolon to prevent weird bugs
- // caused by files not terminating their statements right
- $scripts .= $module->getScript( $context ) . ";\n";
+ // If we are in debug mode, we'll want to return an array of URLs if possible
+ // However, we can't do this if the module doesn't support it
+ // We also can't do this if there is an only= parameter, because we have to give
+ // the module a way to return a load.php URL without causing an infinite loop
+ if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
+ $scripts = $module->getScriptURLsForDebug( $context );
+ } else {
+ $scripts = $module->getScript( $context );
+ if ( is_string( $scripts ) ) {
+ // bug 27054: Append semicolon to prevent weird bugs
+ // caused by files not terminating their statements right
+ $scripts .= ";\n";
+ }
+ }
}
-
// Styles
$styles = array();
if ( $context->shouldIncludeStyles() ) {
- $styles = $module->getStyles( $context );
+ // If we are in debug mode, we'll want to return an array of URLs
+ // See comment near shouldIncludeScripts() for more details
+ if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
+ $styles = $module->getStyleURLsForDebug( $context );
+ } else {
+ $styles = $module->getStyles( $context );
+ }
}
// Messages
@@ -487,7 +506,11 @@ class ResourceLoader {
// Append output
switch ( $context->getOnly() ) {
case 'scripts':
- $out .= $scripts;
+ if ( is_string( $scripts ) ) {
+ $out .= $scripts;
+ } elseif ( is_array( $scripts ) ) {
+ $out .= self::makeLoaderImplementScript( $name, $scripts, array(), array() );
+ }
break;
case 'styles':
$out .= self::makeCombinedStyles( $styles );
@@ -496,11 +519,13 @@ class ResourceLoader {
$out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
break;
default:
- // Minify CSS before embedding in mediaWiki.loader.implement call
+ // Minify CSS before embedding in mw.loader.implement call
// (unless in debug mode)
if ( !$context->getDebug() ) {
foreach ( $styles as $media => $style ) {
- $styles[$media] = $this->filter( 'minify-css', $style );
+ if ( is_string( $style ) ) {
+ $styles[$media] = $this->filter( 'minify-css', $style );
+ }
}
}
$out .= self::makeLoaderImplementScript( $name, $scripts, $styles,
@@ -521,10 +546,10 @@ class ResourceLoader {
// Update module states
if ( $context->shouldIncludeScripts() ) {
// Set the state of modules loaded as only scripts to ready
- if ( count( $modules ) && $context->getOnly() === 'scripts'
- && !isset( $modules['startup'] ) )
+ if ( count( $modules ) && $context->getOnly() === 'scripts'
+ && !isset( $modules['startup'] ) )
{
- $out .= self::makeLoaderStateScript(
+ $out .= self::makeLoaderStateScript(
array_fill_keys( array_keys( $modules ), 'ready' ) );
}
// Set the state of modules which were requested but unavailable as missing
@@ -540,7 +565,7 @@ class ResourceLoader {
$out = $this->filter( 'minify-js', $out );
}
}
-
+
wfProfileOut( __METHOD__ );
return $exceptions . $out;
}
@@ -548,26 +573,30 @@ class ResourceLoader {
/* Static Methods */
/**
- * Returns JS code to call to mediaWiki.loader.implement for a module with
+ * Returns JS code to call to mw.loader.implement for a module with
* given properties.
*
* @param $name Module name
- * @param $scripts Array: List of JavaScript code snippets to be executed after the
- * module is loaded
- * @param $styles Array: List of CSS strings keyed by media type
- * @param $messages Mixed: List of messages associated with this module. May either be an
+ * @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code
+ * @param $styles Mixed: List of CSS strings keyed by media type, or list 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.
+ *
+ * @return string
*/
public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
- if ( is_array( $scripts ) ) {
- $scripts = implode( $scripts, "\n" );
+ if ( is_string( $scripts ) ) {
+ $scripts = new XmlJsCode( "function( $ ) {{$scripts}}" );
+ } elseif ( !is_array( $scripts ) ) {
+ throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
}
- return Xml::encodeJsCall(
- 'mediaWiki.loader.implement',
+ return Xml::encodeJsCall(
+ 'mw.loader.implement',
array(
$name,
- new XmlJsCode( "function( $, mw ) {{$scripts}}" ),
+ $scripts,
(object)$styles,
(object)$messages
) );
@@ -578,16 +607,20 @@ class ResourceLoader {
*
* @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.
+ *
+ * @return string
*/
public static function makeMessageSetScript( $messages ) {
- return Xml::encodeJsCall( 'mediaWiki.messages.set', array( (object)$messages ) );
+ return Xml::encodeJsCall( 'mw.messages.set', array( (object)$messages ) );
}
/**
- * Combines an associative array mapping media type to CSS into a
+ * Combines an associative array mapping media type to CSS into a
* single stylesheet with @media blocks.
*
* @param $styles Array: List of CSS strings keyed by media type
+ *
+ * @return string
*/
public static function makeCombinedStyles( array $styles ) {
$out = '';
@@ -595,10 +628,10 @@ class ResourceLoader {
// Transform the media type based on request params and config
// The way that this relies on $wgRequest to propagate request params is slightly evil
$media = OutputPage::transformCssMedia( $media );
-
+
if ( $media === null ) {
// Skip
- } else if ( $media === '' || $media == 'all' ) {
+ } elseif ( $media === '' || $media == 'all' ) {
// Don't output invalid or frivolous @media statements
$out .= "$style\n";
} else {
@@ -609,7 +642,7 @@ class ResourceLoader {
}
/**
- * Returns a JS call to mediaWiki.loader.state, which sets the state of a
+ * Returns a JS call to mw.loader.state, which sets the state of a
* module or modules to a given value. Has two calling conventions:
*
* - ResourceLoader::makeLoaderStateScript( $name, $state ):
@@ -617,36 +650,43 @@ 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
+ *
+ * @return string
*/
public static function makeLoaderStateScript( $name, $state = null ) {
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mediaWiki.loader.state', array( $name ) );
+ return Xml::encodeJsCall( 'mw.loader.state', array( $name ) );
} else {
- return Xml::encodeJsCall( 'mediaWiki.loader.state', array( $name, $state ) );
+ return Xml::encodeJsCall( 'mw.loader.state', array( $name, $state ) );
}
}
/**
* Returns JS code which calls the script given by $script. The script will
- * be called with local variables name, version, dependencies and group,
- * which will have values corresponding to $name, $version, $dependencies
- * and $group as supplied.
+ * be called with local variables name, version, dependencies and group,
+ * which will have values corresponding to $name, $version, $dependencies
+ * and $group as supplied.
*
* @param $name String: Module name
* @param $version Integer: Module version number as a timestamp
* @param $dependencies Array: List of module names on which this module depends
* @param $group String: Group which the module is in.
* @param $script String: JavaScript code
+ *
+ * @return string
*/
public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $script ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
- return Xml::encodeJsCall(
+ return Xml::encodeJsCall(
"( function( name, version, dependencies, group ) {\n\t$script\n} )",
array( $name, $version, $dependencies, $group ) );
}
/**
- * Returns JS code which calls mediaWiki.loader.register with the given
+ * Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
*
* - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group ):
@@ -666,43 +706,49 @@ class ResourceLoader {
* @param $version Integer: Module version number as a timestamp
* @param $dependencies Array: List of module names on which this module depends
* @param $group String: group which the module is in.
+ *
+ * @return string
*/
- public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null )
+ public static function makeLoaderRegisterScript( $name, $version = null,
+ $dependencies = null, $group = null )
{
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mediaWiki.loader.register', array( $name ) );
+ return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
} else {
$version = (int) $version > 1 ? (int) $version : 1;
- return Xml::encodeJsCall( 'mediaWiki.loader.register',
+ return Xml::encodeJsCall( 'mw.loader.register',
array( $name, $version, $dependencies, $group ) );
}
}
/**
- * Returns JS code which runs given JS code if the client-side framework is
+ * Returns JS code which runs given JS code if the client-side framework is
* present.
*
* @param $script String: JavaScript code
+ *
+ * @return string
*/
public static function makeLoaderConditionalScript( $script ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
- return "if ( window.mediaWiki ) {\n\t$script\n}\n";
+ return "if(window.mw){\n\t$script\n}\n";
}
/**
- * Returns JS code which will set the MediaWiki configuration array to
+ * Returns JS code which will set the MediaWiki configuration array to
* the given value.
*
* @param $configuration Array: List of configuration values keyed by variable name
+ *
+ * @return string
*/
public static function makeConfigSetScript( array $configuration ) {
- return Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) );
+ return Xml::encodeJsCall( 'mw.config.set', array( $configuration ) );
}
-
+
/**
* Convert an array of module names to a packed query string.
- *
+ *
* For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' )
* becomes 'foo.bar,baz|bar.baz,quux'
* @param $modules array of module names (strings)
@@ -716,15 +762,16 @@ class ResourceLoader {
$suffix = $pos === false ? $module : substr( $module, $pos + 1 );
$groups[$prefix][] = $suffix;
}
-
+
$arr = array();
foreach ( $groups as $prefix => $suffixes ) {
$p = $prefix === '' ? '' : $prefix . '.';
$arr[] = $p . implode( ',', $suffixes );
}
- return implode( '|', $arr );
+ $str = implode( '|', $arr );
+ return $str;
}
-
+
/**
* Determine whether debug mode was requested
* Order of priority is 1) request param, 2) cookie, 3) $wg setting
@@ -733,9 +780,71 @@ class ResourceLoader {
public static function inDebugMode() {
global $wgRequest, $wgResourceLoaderDebug;
static $retval = null;
- if ( !is_null( $retval ) )
+ if ( !is_null( $retval ) ) {
return $retval;
+ }
return $retval = $wgRequest->getFuzzyBool( 'debug',
$wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug ) );
}
+
+ /**
+ * Build a load.php URL
+ * @param $modules array of module names (strings)
+ * @param $lang string Language code
+ * @param $skin string Skin name
+ * @param $user string|null User name. If null, the &user= parameter is omitted
+ * @param $version string|null Versioning timestamp
+ * @param $debug bool Whether the request should be in debug mode
+ * @param $only string|null &only= parameter
+ * @param $printable bool Printable mode
+ * @param $handheld bool Handheld mode
+ * @param $extraQuery array 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() ) {
+ global $wgLoadScript;
+ $query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug,
+ $only, $printable, $handheld, $extraQuery
+ );
+
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ return wfExpandUrl( wfAppendQuery( $wgLoadScript, $query ) . '&*', PROTO_RELATIVE );
+ }
+
+ /**
+ * Build a query array (array representation of query string) for load.php. Helper
+ * function for makeLoaderURL().
+ * @return 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,
+ 'skin' => $skin,
+ 'debug' => $debug ? 'true' : 'false',
+ );
+ if ( $user !== null ) {
+ $query['user'] = $user;
+ }
+ if ( $version !== null ) {
+ $query['version'] = $version;
+ }
+ if ( $only !== null ) {
+ $query['only'] = $only;
+ }
+ if ( $printable ) {
+ $query['printable'] = 1;
+ }
+ if ( $handheld ) {
+ $query['handheld'] = 1;
+ }
+ $query += $extraQuery;
+
+ // Make queries uniform in order
+ ksort( $query );
+ return $query;
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index bf059b46..326b7c4a 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -73,6 +73,8 @@ class ResourceLoaderContext {
*/
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 ) {
@@ -98,18 +100,30 @@ class ResourceLoaderContext {
return $retval;
}
+ /**
+ * @return ResourceLoader
+ */
public function getResourceLoader() {
return $this->resourceLoader;
}
-
+
+ /**
+ * @return WebRequest
+ */
public function getRequest() {
return $this->request;
}
+ /**
+ * @return array
+ */
public function getModules() {
return $this->modules;
}
+ /**
+ * @return string
+ */
public function getLanguage() {
if ( $this->language === null ) {
global $wgLang;
@@ -121,49 +135,79 @@ class ResourceLoaderContext {
return $this->language;
}
+ /**
+ * @return string
+ */
public function getDirection() {
if ( $this->direction === null ) {
$this->direction = $this->request->getVal( 'dir' );
if ( !$this->direction ) {
- global $wgContLang;
- $this->direction = $wgContLang->getDir();
+ # directionality based on user language (see bug 6100)
+ $this->direction = Language::factory( $this->language )->getDir();
}
}
return $this->direction;
}
+ /**
+ * @return string
+ */
public function getSkin() {
return $this->skin;
}
+ /**
+ * @return string
+ */
public function getUser() {
return $this->user;
}
+ /**
+ * @return bool
+ */
public function getDebug() {
return $this->debug;
}
+ /**
+ * @return String
+ */
public function getOnly() {
return $this->only;
}
+ /**
+ * @return String
+ */
public function getVersion() {
return $this->version;
}
+ /**
+ * @return bool
+ */
public function shouldIncludeScripts() {
return is_null( $this->only ) || $this->only === 'scripts';
}
+ /**
+ * @return bool
+ */
public function shouldIncludeStyles() {
return is_null( $this->only ) || $this->only === 'styles';
}
+ /**
+ * @return bool
+ */
public function shouldIncludeMessages() {
return is_null( $this->only ) || $this->only === 'messages';
}
+ /**
+ * @return string
+ */
public function getHash() {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index 1c37eb07..f38c60ae 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -78,6 +78,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected $messages = array();
/** String: Name of group to load this module in */
protected $group;
+ /** String: Position on the page to load this module at */
+ protected $position = 'bottom';
/** Boolean: Link to raw files in debug mode */
protected $debugRaw = true;
/**
@@ -95,15 +97,16 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Constructs a new module from an options array.
- *
+ *
* @param $options Array: List of options; if not given or empty, an empty module will be
* constructed
* @param $localBasePath String: Base path to prepend to all local paths in $options. Defaults
* to $IP
* @param $remoteBasePath String: Base path to prepend to all remote paths in $options. Defaults
* to $wgScriptPath
- *
- * @example $options
+ *
+ * Below is a description for the $options array:
+ * @code
* array(
* // Base path to prepend to all local paths in $options. Defaults to $IP
* 'localBasePath' => [base path],
@@ -137,14 +140,21 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* 'messages' => [array of message key strings],
* // Group which this module should be loaded together with
* 'group' => [group name string],
+ * // Position on the page to load this module at
+ * 'position' => ['bottom' (default) or 'top']
* )
+ * @endcode
*/
- public function __construct( $options = array(), $localBasePath = null,
- $remoteBasePath = null )
+ public function __construct( $options = array(), $localBasePath = null,
+ $remoteBasePath = null )
{
- global $IP, $wgScriptPath;
+ global $IP, $wgScriptPath, $wgResourceBasePath;
$this->localBasePath = $localBasePath === null ? $IP : $localBasePath;
- $this->remoteBasePath = $remoteBasePath === null ? $wgScriptPath : $remoteBasePath;
+ if ( $remoteBasePath !== null ) {
+ $this->remoteBasePath = $remoteBasePath;
+ } else {
+ $this->remoteBasePath = $wgResourceBasePath === null ? $wgScriptPath : $wgResourceBasePath;
+ }
if ( isset( $options['remoteExtPath'] ) ) {
global $wgExtensionAssetsPath;
@@ -166,14 +176,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'skinStyles':
if ( !is_array( $option ) ) {
throw new MWException(
- "Invalid collated file path list error. " .
+ "Invalid collated file path list error. " .
"'$option' given, array expected."
);
}
foreach ( $option as $key => $value ) {
if ( !is_string( $key ) ) {
throw new MWException(
- "Invalid collated file path list key error. " .
+ "Invalid collated file path list key error. " .
"'$key' given, string expected."
);
}
@@ -187,6 +197,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
// Single strings
case 'group':
+ case 'position':
case 'localBasePath':
case 'remoteBasePath':
$this->{$member} = (string) $option;
@@ -197,39 +208,37 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
}
}
- // Make sure the remote base path is a complete valid url
- $this->remoteBasePath = wfExpandUrl( $this->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 );
}
/**
* Gets all scripts for a given context concatenated together.
- *
+ *
* @param $context ResourceLoaderContext: Context in which to generate script
* @return String: JavaScript code for $context
*/
public function getScript( ResourceLoaderContext $context ) {
- $files = array_merge(
- $this->scripts,
- self::tryForKey( $this->languageScripts, $context->getLanguage() ),
- self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
- );
- if ( $context->getDebug() ) {
- $files = array_merge( $files, $this->debugScripts );
- if ( $this->debugRaw ) {
- $script = '';
- foreach ( $files as $file ) {
- $path = $this->getRemotePath( $file );
- $script .= "\n\t" . Xml::encodeJsCall( 'mediaWiki.loader.load', array( $path ) );
- }
- return $script;
- }
- }
+ $files = $this->getScriptFiles( $context );
return $this->readScriptFiles( $files );
}
+
+ public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
+ $urls = array();
+ foreach ( $this->getScriptFiles( $context ) as $file ) {
+ $urls[] = $this->getRemotePath( $file );
+ }
+ return $urls;
+ }
+
+ public function supportsURLLoading() {
+ return $this->debugRaw;
+ }
/**
* Gets loader script.
- *
+ *
* @return String: JavaScript code to be added to startup module
*/
public function getLoaderScript() {
@@ -241,29 +250,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets all styles for a given context concatenated together.
- *
+ *
* @param $context ResourceLoaderContext: Context in which to generate styles
* @return String: CSS code for $context
*/
public function getStyles( ResourceLoaderContext $context ) {
- // Merge general styles and skin specific styles, retaining media type collation
- $styles = $this->readStyleFiles( $this->styles, $this->getFlip( $context ) );
- $skinStyles = $this->readStyleFiles(
- self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
+ $styles = $this->readStyleFiles(
+ $this->getStyleFiles( $context ),
$this->getFlip( $context )
);
-
- foreach ( $skinStyles as $media => $style ) {
- if ( isset( $styles[$media] ) ) {
- $styles[$media] .= $style;
- } else {
- $styles[$media] = $style;
- }
- }
// Collect referenced files
$this->localFileRefs = array_unique( $this->localFileRefs );
// If the list has been modified since last time we cached it, update the cache
- if ( $this->localFileRefs !== $this->getFileDependencies( $context->getSkin() ) ) {
+ if ( $this->localFileRefs !== $this->getFileDependencies( $context->getSkin() ) && !wfReadOnly() ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'module_deps',
array( array( 'md_module', 'md_skin' ) ), array(
@@ -276,9 +275,20 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $styles;
}
+ public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
+ $urls = array();
+ foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
+ $urls[$mediaType] = array();
+ foreach ( $list as $file ) {
+ $urls[$mediaType][] = $this->getRemotePath( $file );
+ }
+ }
+ return $urls;
+ }
+
/**
* Gets list of message keys used by this module.
- *
+ *
* @return Array: List of message keys
*/
public function getMessages() {
@@ -287,7 +297,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the name of the group this module should be loaded in.
- *
+ *
* @return String: Group name
*/
public function getGroup() {
@@ -295,8 +305,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * @return string
+ */
+ public function getPosition() {
+ return $this->position;
+ }
+
+ /**
* Gets list of names of modules this module depends on.
- *
+ *
* @return Array: List of module names
*/
public function getDependencies() {
@@ -305,14 +322,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Get the last modified timestamp of this module.
- *
- * Last modified timestamps are calculated from the highest last modified
- * timestamp of this module's constituent files as well as the files it
- * depends on. This function is context-sensitive, only performing
- * calculations on files relevant to the given language, skin and debug
+ *
+ * Last modified timestamps are calculated from the highest last modified
+ * timestamp of this module's constituent files as well as the files it
+ * depends on. This function is context-sensitive, only performing
+ * calculations on files relevant to the given language, skin and debug
* mode.
- *
- * @param $context ResourceLoaderContext: Context in which to calculate
+ *
+ * @param $context ResourceLoaderContext: Context in which to calculate
* the modified time
* @return Integer: UNIX timestamp
* @see ResourceLoaderModule::getFileDependencies
@@ -322,23 +339,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $this->modifiedTime[$context->getHash()];
}
wfProfileIn( __METHOD__ );
-
+
$files = array();
-
+
// Flatten style files into $files
$styles = self::collateFilePathListByOption( $this->styles, 'media', 'all' );
foreach ( $styles as $styleFiles ) {
$files = array_merge( $files, $styleFiles );
}
$skinFiles = self::tryForKey(
- self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ),
- $context->getSkin(),
+ self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ),
+ $context->getSkin(),
'default'
);
foreach ( $skinFiles as $styleFiles ) {
$files = array_merge( $files, $styleFiles );
}
-
+
// Final merge, this should result in a master list of dependent files
$files = array_merge(
$files,
@@ -351,39 +368,47 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$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() ) );
-
- // If a module is nothing but a list of dependencies, we need to avoid
+
+ // If a module is nothing but a list of dependencies, we need to avoid
// giving max() an empty array
if ( count( $files ) === 0 ) {
wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()] = 1;
}
-
+
wfProfileIn( __METHOD__.'-filemtime' );
- $filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
+ $filesMtime = max( array_map( 'filemtime', $files ) );
wfProfileOut( __METHOD__.'-filemtime' );
- $this->modifiedTime[$context->getHash()] = max(
- $filesMtime,
+ $this->modifiedTime[$context->getHash()] = max(
+ $filesMtime,
$this->getMsgBlobMtime( $context->getLanguage() ) );
wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()];
}
- /* Protected Members */
+ /* Protected Methods */
+ /**
+ * @param $path string
+ * @return string
+ */
protected function getLocalPath( $path ) {
return "{$this->localBasePath}/$path";
}
-
+
+ /**
+ * @param $path string
+ * @return string
+ */
protected function getRemotePath( $path ) {
return "{$this->remoteBasePath}/$path";
}
/**
* Collates file paths by option (where provided).
- *
- * @param $list Array: List of file paths in any combination of index/path
+ *
+ * @param $list Array: List of file paths in any combination of index/path
* or path/options pairs
* @param $option String: option name
* @param $default Mixed: default value if the option isn't set
@@ -398,7 +423,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$collatedFiles[$default] = array();
}
$collatedFiles[$default][] = $value;
- } else if ( is_array( $value ) ) {
+ } elseif ( is_array( $value ) ) {
// File name as the key, options array as the value
$optionValue = isset( $value[$option] ) ? $value[$option] : $default;
if ( !isset( $collatedFiles[$optionValue] ) ) {
@@ -412,19 +437,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets a list of element that match a key, optionally using a fallback key.
- *
+ *
* @param $list Array: List of lists to select from
* @param $key String: Key to look for in $map
* @param $fallback String: Key to look for in $list if $key doesn't exist
- * @return Array: List of elements from $map which matched $key or $fallback,
+ * @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];
- } else if ( is_string( $fallback )
- && isset( $list[$fallback] )
- && is_array( $list[$fallback] ) )
+ } elseif ( is_string( $fallback )
+ && isset( $list[$fallback] )
+ && is_array( $list[$fallback] ) )
{
return $list[$fallback];
}
@@ -432,23 +457,56 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * Gets a list of file paths for all scripts in this module, in order of propper execution.
+ *
+ * @param $context ResourceLoaderContext: Context
+ * @return Array: List of file paths
+ */
+ protected function getScriptFiles( ResourceLoaderContext $context ) {
+ $files = array_merge(
+ $this->scripts,
+ self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
+ );
+ if ( $context->getDebug() ) {
+ $files = array_merge( $files, $this->debugScripts );
+ }
+ return $files;
+ }
+
+ /**
+ * Gets a list of file paths for all styles in this module, in order of propper inclusion.
+ *
+ * @param $context ResourceLoaderContext: Context
+ * @return Array: List of file paths
+ */
+ protected 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'
+ )
+ );
+ }
+
+ /**
* Gets the contents of a list of JavaScript files.
- *
+ *
* @param $scripts Array: List of file paths to scripts to read, remap and concetenate
* @return String: Concatenated and remapped JavaScript data from $scripts
*/
protected function readScriptFiles( array $scripts ) {
+ global $wgResourceLoaderValidateStaticJS;
if ( empty( $scripts ) ) {
return '';
}
- global $wgResourceLoaderValidateStaticJS;
$js = '';
foreach ( array_unique( $scripts ) as $fileName ) {
$localPath = $this->getLocalPath( $fileName );
- if ( !file_exists( $localPath ) ) {
+ $contents = file_get_contents( $localPath );
+ if ( $contents === false ) {
throw new MWException( __METHOD__.": script file not found: \"$localPath\"" );
}
- $contents = file_get_contents( $localPath );
if ( $wgResourceLoaderValidateStaticJS ) {
// Static files don't really need to be checked as often; unlike
// on-wiki module they shouldn't change unexpectedly without
@@ -462,16 +520,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the contents of a list of CSS files.
- *
- * @param $styles Array: List of file paths to styles to read, remap and concetenate
- * @return Array: List of concatenated and remapped CSS data from $styles,
+ *
+ * @param $styles Array: List of media type/list of file paths pairs, to read, remap and
+ * concetenate
+ *
+ * @param $flip bool
+ *
+ * @return Array: List of concatenated and remapped CSS data from $styles,
* keyed by media type
*/
protected function readStyleFiles( array $styles, $flip ) {
if ( empty( $styles ) ) {
return array();
}
- $styles = self::collateFilePathListByOption( $styles, 'media', 'all' );
foreach ( $styles as $media => $files ) {
$uniqueFiles = array_unique( $files );
$styles[$media] = implode(
@@ -488,49 +549,38 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Reads a style file.
- *
+ *
* This method can be used as a callback for array_map()
- *
- * @param $path String: File path of style file to read
+ *
+ * @param $path String: File path of script file to read
+ * @param $flip bool
+ *
* @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 ) {
$localPath = $this->getLocalPath( $path );
- if ( !file_exists( $localPath ) ) {
+ $style = file_get_contents( $localPath );
+ if ( $style === false ) {
throw new MWException( __METHOD__.": style file not found: \"$localPath\"" );
}
- $style = file_get_contents( $localPath );
if ( $flip ) {
$style = CSSJanus::transform( $style, true, false );
}
- $dir = $this->getLocalPath( dirname( $path ) );
- $remoteDir = $this->getRemotePath( dirname( $path ) );
+ $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 );
// Get and register local file references
- $this->localFileRefs = array_merge(
- $this->localFileRefs,
+ $this->localFileRefs = array_merge(
+ $this->localFileRefs,
CSSMin::getLocalFileReferences( $style, $dir ) );
return CSSMin::remap(
$style, $dir, $remoteDir, true
);
}
-
- /**
- * Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist
- * but returns 1 instead.
- * @param $filename string File name
- * @return int UNIX timestamp, or 1 if the file doesn't exist
- */
- protected static function safeFilemtime( $filename ) {
- if ( file_exists( $filename ) ) {
- return filemtime( $filename );
- } else {
- // We only ever map this function on an array if we're gonna call max() after,
- // so return our standard minimum timestamps here. This is 1, not 0, because
- // wfTimestamp(0) == NOW
- return 1;
- }
- }
/**
* Get whether CSS for this module should be flipped
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/resourceloader/ResourceLoaderFilePageModule.php
new file mode 100644
index 00000000..fc9aef1b
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderFilePageModule.php
@@ -0,0 +1,11 @@
+<?php
+/*
+ * ResourceLoader definition for MediaWiki:Filepage.css
+ */
+class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
+ protected function getPages( ResourceLoaderContext $context ) {
+ return array(
+ 'MediaWiki:Filepage.css' => array( 'type' => 'style' ),
+ );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 77d230c9..ae1be5af 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -24,6 +24,34 @@
* 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';
+ const TYPE_MESSAGES = 'messages';
+ const TYPE_COMBINED = 'combined';
+
+ # sitewide core module like a skin file or jQuery component
+ const ORIGIN_CORE_SITEWIDE = 1;
+
+ # per-user module generated by the software
+ const ORIGIN_CORE_INDIVIDUAL = 2;
+
+ # sitewide module generated from user-editable files, like MediaWiki:Common.js, or
+ # modules accessible to multiple users, such as those generated by the Gadgets extension.
+ const ORIGIN_USER_SITEWIDE = 3;
+
+ # per-user module generated from user-editable files, like User:Me/vector.js
+ const ORIGIN_USER_INDIVIDUAL = 4;
+
+ # an access constant; make sure this is kept as the largest number in this group
+ const ORIGIN_ALL = 10;
+
+ # script and style modules form a hierarchy of trustworthiness, with core modules like
+ # skins and jQuery as most trustworthy, and user scripts as least trustworthy. We can
+ # limit the types of scripts and styles we allow to load on, say, sensitive special
+ # pages like Special:UserLogin and Special:Preferences
+ protected $origin = self::ORIGIN_CORE_SITEWIDE;
/* Protected Members */
@@ -57,10 +85,34 @@ abstract class ResourceLoaderModule {
}
/**
- * Get whether CSS for this module should be flipped
+ * Get this module's origin. This is set when the module is registered
+ * with ResourceLoader::register()
+ *
+ * @return Int ResourceLoaderModule class constant, the subclass default
+ * if not set manuall
+ */
+ public function getOrigin() {
+ return $this->origin;
+ }
+
+ /**
+ * Set this module's origin. This is called by ResourceLodaer::register()
+ * when registering the module. Other code should not call this.
+ *
+ * @param $origin Int origin
+ */
+ public function setOrigin( $origin ) {
+ $this->origin = $origin;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return bool
*/
public function getFlip( $context ) {
- return $context->getDirection() === 'rtl';
+ global $wgContLang;
+
+ return $wgContLang->getDir() !== $context->getDirection();
}
/**
@@ -74,6 +126,44 @@ abstract class ResourceLoaderModule {
// Stub, override expected
return '';
}
+
+ /**
+ * 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
+ * load the files directly.
+ *
+ * This function is called only when 1) we're in debug mode, 2) there
+ * is no only= parameter and 3) supportsURLLoading() returns true.
+ * #2 is important to prevent an infinite loop, therefore this function
+ * MUST return either an only= URL or a non-load.php URL.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array of URLs
+ */
+ public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
+ global $wgLoadScript; // TODO factor out to ResourceLoader static method and deduplicate from makeResourceLoaderLink()
+ $query = array(
+ 'modules' => $this->getName(),
+ 'only' => 'scripts',
+ 'skin' => $context->getSkin(),
+ 'user' => $context->getUser(),
+ 'debug' => 'true',
+ 'version' => $context->getVersion()
+ );
+ ksort( $query );
+ return array( wfAppendQuery( $wgLoadScript, $query ) . '&*' );
+ }
+
+ /**
+ * Whether this module supports URL loading. If this function returns false,
+ * getScript() will be used even in cases (debug mode, no only param) where
+ * getScriptURLsForDebug() would normally be used instead.
+ * @return bool
+ */
+ public function supportsURLLoading() {
+ return true;
+ }
/**
* Get all CSS for this module for a given skin.
@@ -83,7 +173,30 @@ abstract class ResourceLoaderModule {
*/
public function getStyles( ResourceLoaderContext $context ) {
// Stub, override expected
- return '';
+ return array();
+ }
+
+ /**
+ * Get the URL or URLs to load for this module's CSS in debug mode.
+ * The default behavior is to return a load.php?only=styles URL for
+ * the module, but file-based modules will want to override this to
+ * load the files directly. See also getScriptURLsForDebug()
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array: array( mediaType => array( URL1, URL2, ... ), ... )
+ */
+ public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
+ global $wgLoadScript; // TODO factor out to ResourceLoader static method and deduplicate from makeResourceLoaderLink()
+ $query = array(
+ 'modules' => $this->getName(),
+ 'only' => 'styles',
+ 'skin' => $context->getSkin(),
+ 'user' => $context->getUser(),
+ 'debug' => 'true',
+ 'version' => $context->getVersion()
+ );
+ ksort( $query );
+ return array( 'all' => array( wfAppendQuery( $wgLoadScript, $query ) . '&*' ) );
}
/**
@@ -107,6 +220,17 @@ abstract class ResourceLoaderModule {
// Stub, override expected
return null;
}
+
+ /**
+ * Where on the HTML page should this module's JS be loaded?
+ * 'top': in the <head>
+ * 'bottom': at the bottom of the <body>
+ *
+ * @return string
+ */
+ public function getPosition() {
+ return 'bottom';
+ }
/**
* Get the loader JS for this module, if set.
@@ -179,7 +303,7 @@ abstract class ResourceLoaderModule {
* Get the last modification timestamp of the message blob for this
* module in a given language.
* @param $lang String: Language code
- * @return Integer: UNIX timestamp, or 0 if no blob found
+ * @return Integer: UNIX timestamp, or 0 if the module doesn't have messages
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
@@ -192,7 +316,12 @@ abstract class ResourceLoaderModule {
'mr_lang' => $lang
), __METHOD__
);
- $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
+ // If no blob was found, but the module does have messages, that means we need
+ // to regenerate it. Return NOW
+ if ( $msgBlobMtime === false ) {
+ $msgBlobMtime = wfTimestampNow();
+ }
+ $this->msgBlobMtime[$lang] = wfTimestamp( TS_UNIX, $msgBlobMtime );
}
return $this->msgBlobMtime[$lang];
}
@@ -236,4 +365,54 @@ abstract class ResourceLoaderModule {
public function isKnownEmpty( ResourceLoaderContext $context ) {
return false;
}
+
+
+ /** @var JSParser lazy-initialized; use self::javaScriptParser() */
+ private static $jsParser;
+ private static $parseCacheVersion = 1;
+
+ /**
+ * Validate a given script file; if valid returns the original source.
+ * If invalid, returns replacement JS source that throws an exception.
+ *
+ * @param string $fileName
+ * @param string $contents
+ * @return string JS with the original, or a replacement error
+ */
+ protected function validateScriptFile( $fileName, $contents ) {
+ global $wgResourceLoaderValidateJS;
+ if ( $wgResourceLoaderValidateJS ) {
+ // Try for cache hit
+ // Use CACHE_ANYTHING since filtering is very slow compared to DB queries
+ $key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) );
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $cacheEntry = $cache->get( $key );
+ if ( is_string( $cacheEntry ) ) {
+ return $cacheEntry;
+ }
+
+ $parser = self::javaScriptParser();
+ try {
+ $parser->parse( $contents, $fileName, 1 );
+ $result = $contents;
+ } catch (Exception $e) {
+ // We'll save this to cache to avoid having to validate broken JS over and over...
+ $err = $e->getMessage();
+ $result = "throw new Error(" . Xml::encodeJsVar("JavaScript parse error: $err") . ");";
+ }
+
+ $cache->set( $key, $result );
+ return $result;
+ } else {
+ return $contents;
+ }
+ }
+
+ protected static function javaScriptParser() {
+ if ( !self::$jsParser ) {
+ self::$jsParser = new JSParser();
+ }
+ return self::$jsParser;
+ }
+
}
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderNoscriptModule.php
new file mode 100644
index 00000000..28f629a2
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderNoscriptModule.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * 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 Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Module for site customizations
+ */
+class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
+
+ /* Protected Methods */
+
+ /**
+ * Gets list of pages used by this module. Obviously, it makes absolutely no
+ * sense to include JavaScript files here... :D
+ *
+ * @param $context ResourceLoaderContext
+ *
+ * @return Array: List of pages
+ */
+ protected function getPages( ResourceLoaderContext $context ) {
+ return array( 'MediaWiki:Noscript.css' => array( 'type' => 'style' ) );
+ }
+
+ /* Methods */
+
+ /**
+ * Gets group name
+ *
+ * @return String: Name of group
+ */
+ public function getGroup() {
+ return 'noscript';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 977d16bb..2527a0a3 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -29,7 +29,9 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets list of pages used by this module
- *
+ *
+ * @param $context ResourceLoaderContext
+ *
* @return Array: List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index f3117378..43f1dbd2 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -21,20 +21,24 @@
*/
class ResourceLoaderStartUpModule extends ResourceLoaderModule {
-
+
/* Protected Members */
protected $modifiedTime = array();
/* Protected Methods */
-
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
protected function getConfig( $context ) {
- global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
- $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
- $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
- $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
+ global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
+ $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
+ $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
+ $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
$wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
- $wgResourceLoaderMaxQueryLength;
+ $wgCookiePrefix, $wgResourceLoaderMaxQueryLength, $wgLegacyJavaScriptGlobals;
// Pre-process information
$separatorTransTable = $wgContLang->separatorTransformTable();
@@ -50,7 +54,21 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
implode( "\t", $digitTransTable ),
);
$mainPage = Title::newMainPage();
-
+
+ /**
+ * Namespace related preparation
+ * - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
+ * - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
+ */
+ $namespaceIds = $wgContLang->getNamespaceIds();
+ $caseSensitiveNamespaces = array();
+ foreach( MWNamespace::getCanonicalNamespaces() as $index => $name ) {
+ $namespaceIds[$wgContLang->lc( $name )] = $index;
+ if ( !MWNamespace::isCapitalized( $index ) ) {
+ $caseSensitiveNamespaces[] = $index;
+ }
+ }
+
// Build list of variables
$vars = array(
'wgLoadScript' => $wgLoadScript,
@@ -70,29 +88,37 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgVersion' => $wgVersion,
'wgEnableAPI' => $wgEnableAPI,
'wgEnableWriteAPI' => $wgEnableWriteAPI,
+ 'wgDefaultDateFormat' => $wgContLang->getDefaultDateFormat(),
+ 'wgMonthNames' => $wgContLang->getMonthNamesArray(),
+ 'wgMonthNamesShort' => $wgContLang->getMonthAbbreviationsArray(),
'wgSeparatorTransformTable' => $compactSeparatorTransTable,
'wgDigitTransformTable' => $compactDigitTransTable,
'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
- 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
+ 'wgNamespaceIds' => $namespaceIds,
'wgSiteName' => $wgSitename,
'wgFileExtensions' => array_values( $wgFileExtensions ),
'wgDBname' => $wgDBname,
+ // This sucks, it is only needed on Special:Upload, but I could
+ // not find a way to add vars only for a certain module
+ 'wgFileCanRotate' => BitmapHandler::canRotate(),
+ 'wgAvailableSkins' => Skin::getSkinNames(),
'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
+ // MediaWiki sets cookies to have this prefix by default
+ 'wgCookiePrefix' => $wgCookiePrefix,
'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
+ 'wgLegacyJavaScriptGlobals' => $wgLegacyJavaScriptGlobals,
+ 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
);
- if ( $wgContLang->hasVariants() ) {
- $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
- }
if ( $wgUseAjax && $wgEnableMWSuggest ) {
$vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
}
-
+
wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
-
+
return $vars;
}
-
+
/**
* Gets registration code for all modules
*
@@ -102,7 +128,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
public static function getModuleRegistrations( ResourceLoaderContext $context ) {
global $wgCacheEpoch;
wfProfileIn( __METHOD__ );
-
+
$out = '';
$registrations = array();
$resourceLoader = $context->getResourceLoader();
@@ -113,8 +139,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
if ( $loader !== false ) {
$deps = $module->getDependencies();
$group = $module->getGroup();
- $version = wfTimestamp( TS_ISO_8601_BASIC,
- round( $module->getModifiedTime( $context ), -2 ) );
+ $version = wfTimestamp( TS_ISO_8601_BASIC,
+ $module->getModifiedTime( $context ) );
$out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
}
// Automatically register module
@@ -123,19 +149,19 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// 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 or a group pass two arguments (name, timestamp) to
- // mediaWiki.loader.register()
+ // Modules without dependencies or a group pass two arguments (name, timestamp) to
+ // mw.loader.register()
if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
$registrations[] = array( $name, $mtime );
}
- // Modules with dependencies but no group pass three arguments
- // (name, timestamp, dependencies) to mediaWiki.loader.register()
- else if ( $module->getGroup() === null ) {
+ // Modules with dependencies but no group pass three arguments
+ // (name, timestamp, dependencies) to mw.loader.register()
+ elseif ( $module->getGroup() === null ) {
$registrations[] = array(
$name, $mtime, $module->getDependencies() );
}
- // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
- // to mediaWiki.loader.register()
+ // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
+ // to mw.loader.register()
else {
$registrations[] = array(
$name, $mtime, $module->getDependencies(), $module->getGroup() );
@@ -143,52 +169,74 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
}
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
-
+
wfProfileOut( __METHOD__ );
return $out;
}
/* Methods */
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
public function getScript( ResourceLoaderContext $context ) {
- global $IP, $wgLoadScript;
+ global $IP, $wgLoadScript, $wgLegacyJavaScriptGlobals;
$out = file_get_contents( "$IP/resources/startup.js" );
if ( $context->getOnly() === 'scripts' ) {
- // Build load query for jquery and mediawiki modules
+
+ // The core modules:
+ $modules = array( 'jquery', 'mediawiki' );
+ wfRunHooks( 'ResourceLoaderGetStartupModules', array( &$modules ) );
+
+ // Get the latest version
+ $version = 0;
+ foreach ( $modules as $moduleName ) {
+ $version = max( $version,
+ $context->getResourceLoader()->getModule( $moduleName )->getModifiedTime( $context )
+ );
+ }
+ // Build load query for StartupModules
$query = array(
- 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
+ 'modules' => ResourceLoader::makePackedModulesString( $modules ),
'only' => 'scripts',
'lang' => $context->getLanguage(),
'skin' => $context->getSkin(),
'debug' => $context->getDebug() ? 'true' : 'false',
- 'version' => wfTimestamp( TS_ISO_8601_BASIC, round( max(
- $context->getResourceLoader()->getModule( 'jquery' )->getModifiedTime( $context ),
- $context->getResourceLoader()->getModule( 'mediawiki' )->getModifiedTime( $context )
- ), -2 ) )
+ 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
);
// Ensure uniform query order
ksort( $query );
-
+
// Startup function
$configuration = $this->getConfig( $context );
$registrations = self::getModuleRegistrations( $context );
- $out .= "var startUp = function() {\n" .
- "\t$registrations\n" .
- "\t" . Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) ) .
+ $out .= "var startUp = function() {\n" .
+ "\tmw.config = new " . Xml::encodeJsCall( 'mw.Map', array( $wgLegacyJavaScriptGlobals ) ) . "\n" .
+ "\t$registrations\n" .
+ "\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
"};\n";
-
+
// Conditional script injection
$scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) );
- $out .= "if ( isCompatible() ) {\n" .
- "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
- "}\n" .
+ $out .= "if ( isCompatible() ) {\n" .
+ "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
+ "}\n" .
"delete isCompatible;";
}
return $out;
}
+ public function supportsURLLoading() {
+ return false;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array|mixed
+ */
public function getModifiedTime( ResourceLoaderContext $context ) {
global $IP, $wgCacheEpoch;
@@ -204,7 +252,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$this->modifiedTime[$hash] = filemtime( "$IP/resources/startup.js" );
// ATTENTION!: Because of the line above, this is not going to cause
- // infinite recursion - think carefully before making changes to this
+ // infinite recursion - think carefully before making changes to this
// code!
$time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
foreach ( $loader->getModuleNames() as $name ) {
@@ -214,14 +262,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
return $this->modifiedTime[$hash] = $time;
}
- public function getFlip( $context ) {
- global $wgContLang;
-
- return $wgContLang->getDir() !== $context->getDirection();
- }
-
/* Methods */
-
+
+ /**
+ * @return string
+ */
public function getGroup() {
return 'startup';
}
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
new file mode 100644
index 00000000..733dfa04
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * 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
+ */
+
+/**
+ * Module for user customizations
+ */
+class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
+
+ /* Protected Methods */
+ protected $origin = self::ORIGIN_USER_SITEWIDE;
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
+ protected function getPages( ResourceLoaderContext $context ) {
+ if ( $context->getUser() ) {
+ $user = User::newFromName( $context->getUser() );
+ if ( $user instanceof User ) {
+ $pages = array();
+ foreach( $user->getEffectiveGroups() as $group ) {
+ if ( in_array( $group, array( '*', 'user' ) ) ) {
+ continue;
+ }
+ $pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' );
+ $pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' );
+ }
+ return $pages;
+ }
+ }
+ return array();
+ }
+
+ /* Methods */
+
+ /**
+ * @return string
+ */
+ public function getGroup() {
+ return 'user';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index c7186653..892e8462 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -26,7 +26,12 @@
class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
/* Protected Methods */
+ protected $origin = self::ORIGIN_USER_INDIVIDUAL;
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
protected function getPages( ResourceLoaderContext $context ) {
if ( $context->getUser() ) {
$username = $context->getUser();
@@ -41,9 +46,12 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
}
return array();
}
-
+
/* Methods */
-
+
+ /**
+ * @return string
+ */
public function getGroup() {
return 'user';
}
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 61c76940..8f28eb8d 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -29,8 +29,14 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
protected $modifiedTime = array();
+ protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+
/* Methods */
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array|int|Mixed
+ */
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
if ( isset( $this->modifiedTime[$hash] ) ) {
@@ -64,11 +70,19 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
}
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mediaWiki.user.options.set',
+ return Xml::encodeJsCall( 'mw.user.options.set',
array( $this->contextUserOptions( $context ) ) );
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
public function getStyles( ResourceLoaderContext $context ) {
global $wgAllowUserCssPrefs;
@@ -80,6 +94,10 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
if ( $options['underline'] < 2 ) {
$rules[] = "a { text-decoration: " .
( $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(fa),a:lang(kk-arab), ' .
+ 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }';
}
if ( $options['highlightbroken'] ) {
$rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
@@ -109,12 +127,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
return array();
}
- public function getFlip( $context ) {
- global $wgContLang;
-
- return $wgContLang->getDir() !== $context->getDirection();
- }
-
+ /**
+ * @return string
+ */
public function getGroup() {
return 'private';
}
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
new file mode 100644
index 00000000..9403534c
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * 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 Krinkle
+ */
+
+/**
+ * Module for user tokens
+ */
+class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+
+ /* Methods */
+
+ /**
+ * Fetch the tokens for the current user.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array: List of tokens keyed by token type
+ */
+ protected function contextUserTokens( ResourceLoaderContext $context ) {
+ global $wgUser;
+
+ return array(
+ 'editToken' => $wgUser->edittoken(),
+ 'watchToken' => ApiQueryInfo::getWatchToken(null, null),
+ );
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall( 'mw.user.tokens.set',
+ array( $this->contextUserTokens( $context ) ) );
+ }
+
+ /**
+ * @return string
+ */
+ public function getGroup() {
+ return 'private';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index 93e66eb0..bad61cb9 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -32,6 +32,9 @@ defined( 'MEDIAWIKI' ) || die( 1 );
abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/* Protected Members */
+
+ # Origin is user-supplied code
+ protected $origin = self::ORIGIN_USER_SITEWIDE;
// In-object cache for title mtimes
protected $titleMtimes = array();
@@ -41,11 +44,15 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
abstract protected function getPages( ResourceLoaderContext $context );
/* Protected Methods */
-
+
+ /**
+ * @param $title Title
+ * @return null|string
+ */
protected function getContent( $title ) {
if ( $title->getNamespace() === NS_MEDIAWIKI ) {
- $dbkey = $title->getDBkey();
- return wfEmptyMsg( $dbkey ) ? '' : wfMsgExt( $dbkey, 'content' );
+ $message = wfMessage( $title->getDBkey() )->inContentLanguage();
+ return $message->exists() ? $message->plain() : '';
}
if ( !$title->isCssJsSubpage() ) {
return null;
@@ -59,6 +66,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/* Methods */
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
public function getScript( ResourceLoaderContext $context ) {
$scripts = '';
foreach ( $this->getPages( $context ) as $titleText => $options ) {
@@ -66,11 +77,12 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
continue;
}
$title = Title::newFromText( $titleText );
- if ( !$title ) {
+ if ( !$title || $title->isRedirect() ) {
continue;
}
$script = $this->getContent( $title );
if ( strval( $script ) !== '' ) {
+ $script = $this->validateScriptFile( $titleText, $script );
if ( strpos( $titleText, '*/' ) === false ) {
$scripts .= "/* $titleText */\n";
}
@@ -80,6 +92,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return $scripts;
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
public function getStyles( ResourceLoaderContext $context ) {
global $wgScriptPath;
@@ -89,7 +105,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
continue;
}
$title = Title::newFromText( $titleText );
- if ( !$title ) {
+ if ( !$title || $title->isRedirect() ) {
continue;
}
$media = isset( $options['media'] ) ? $options['media'] : 'all';
@@ -112,6 +128,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return $styles;
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return int|mixed
+ */
public function getModifiedTime( ResourceLoaderContext $context ) {
$modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
$mtimes = $this->getTitleMtimes( $context );
@@ -120,21 +140,15 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
return $modifiedTime;
}
-
- public function isKnownEmpty( ResourceLoaderContext $context ) {
- return count( $this->getTitleMtimes( $context ) ) == 0;
- }
-
+
/**
* @param $context ResourceLoaderContext
* @return bool
*/
- public function getFlip( $context ) {
- global $wgContLang;
-
- return $wgContLang->getDir() !== $context->getDirection();
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ return count( $this->getTitleMtimes( $context ) ) == 0;
}
-
+
/**
* Get the modification times of all titles that would be loaded for
* a given context.