summaryrefslogtreecommitdiff
path: root/includes/resourceloader
diff options
context:
space:
mode:
Diffstat (limited to 'includes/resourceloader')
-rw-r--r--includes/resourceloader/DerivativeResourceLoaderContext.php1
-rw-r--r--includes/resourceloader/ResourceLoader.php323
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php91
-rw-r--r--includes/resourceloader/ResourceLoaderEditToolbarModule.php1
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php109
-rw-r--r--includes/resourceloader/ResourceLoaderFilePageModule.php37
-rw-r--r--includes/resourceloader/ResourceLoaderImage.php388
-rw-r--r--includes/resourceloader/ResourceLoaderImageModule.php327
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageDataModule.php12
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageNamesModule.php14
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php87
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php13
-rw-r--r--includes/resourceloader/ResourceLoaderSkinModule.php92
-rw-r--r--includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php107
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php118
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php14
-rw-r--r--includes/resourceloader/ResourceLoaderUserDefaultsModule.php (renamed from includes/resourceloader/ResourceLoaderNoscriptModule.php)46
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php26
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php36
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php15
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php13
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php90
22 files changed, 1550 insertions, 410 deletions
diff --git a/includes/resourceloader/DerivativeResourceLoaderContext.php b/includes/resourceloader/DerivativeResourceLoaderContext.php
index d114d7ed..5784f2a0 100644
--- a/includes/resourceloader/DerivativeResourceLoaderContext.php
+++ b/includes/resourceloader/DerivativeResourceLoaderContext.php
@@ -126,6 +126,7 @@ class DerivativeResourceLoaderContext extends ResourceLoaderContext {
public function setUser( $user ) {
$this->user = $user;
$this->hash = null;
+ $this->userObj = null;
}
public function getDebug() {
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 4f1414bc..150ccd07 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -35,26 +35,47 @@ class ResourceLoader {
/** @var bool */
protected static $debugMode = null;
- /** @var array Module name/ResourceLoaderModule object pairs */
+ /** @var array */
+ private static $lessVars = null;
+
+ /**
+ * Module name/ResourceLoaderModule object pairs
+ * @var array
+ */
protected $modules = array();
- /** @var array Associative array mapping module name to info associative array */
+ /**
+ * Associative array mapping module name to info associative array
+ * @var array
+ */
protected $moduleInfos = array();
/** @var Config $config */
private $config;
/**
- * @var array Associative array mapping framework ids to a list of names of test suite modules
- * like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. )
+ * 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 array
*/
protected $testModuleNames = array();
- /** @var array E.g. array( 'source-id' => 'http://.../load.php' ) */
+ /**
+ * E.g. array( 'source-id' => 'http://.../load.php' )
+ * @var array
+ */
protected $sources = array();
- /** @var bool */
- protected $hasErrors = false;
+ /**
+ * Errors accumulated during current respond() call.
+ * @var array
+ */
+ protected $errors = array();
+
+ /**
+ * @var MessageBlobStore
+ */
+ protected $blobStore;
/**
* Load information stored in the database about modules.
@@ -130,7 +151,7 @@ class ResourceLoader {
foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
$module = $this->getModule( $name );
if ( $module ) {
- $module->setMsgBlobMtime( $lang, 0 );
+ $module->setMsgBlobMtime( $lang, 1 );
}
}
}
@@ -152,12 +173,10 @@ class ResourceLoader {
* @return string Filtered data, or a comment containing an error message
*/
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' ) ) ) {
- wfProfileOut( __METHOD__ );
return $data;
}
@@ -168,7 +187,6 @@ class ResourceLoader {
$cacheEntry = $cache->get( $key );
if ( is_string( $cacheEntry ) ) {
wfIncrStats( "rl-$filter-cache-hits" );
- wfProfileOut( __METHOD__ );
return $cacheEntry;
}
@@ -199,13 +217,9 @@ class ResourceLoader {
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": minification failed: $e" );
- $this->hasErrors = true;
- // Return exception as a comment
- $result = self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
- wfProfileOut( __METHOD__ );
-
return $result;
}
@@ -218,8 +232,6 @@ class ResourceLoader {
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' );
@@ -236,14 +248,14 @@ class ResourceLoader {
// Register core modules
$this->register( include "$IP/resources/Resources.php" );
// Register extension modules
- wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
+ Hooks::run( 'ResourceLoaderRegisterModules', array( &$this ) );
$this->register( $config->get( 'ResourceModules' ) );
if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
$this->registerTestModules();
}
- wfProfileOut( __METHOD__ );
+ $this->setMessageBlobStore( new MessageBlobStore() );
}
/**
@@ -254,6 +266,14 @@ class ResourceLoader {
}
/**
+ * @param MessageBlobStore $blobStore
+ * @since 1.25
+ */
+ public function setMessageBlobStore( MessageBlobStore $blobStore ) {
+ $this->blobStore = $blobStore;
+ }
+
+ /**
* Register a module with the ResourceLoader system.
*
* @param mixed $name Name of module as a string or List of name/object pairs as an array
@@ -267,14 +287,12 @@ class ResourceLoader {
* not registered
*/
public function register( $name, $info = null ) {
- wfProfileIn( __METHOD__ );
// Allow multiple modules to be registered in one call
$registrations = is_array( $name ) ? $name : array( $name => $info );
foreach ( $registrations as $name => $info ) {
// Disallow duplicate registrations
if ( isset( $this->moduleInfos[$name] ) ) {
- wfProfileOut( __METHOD__ );
// A module has already been registered by this name
throw new MWException(
'ResourceLoader duplicate registration error. ' .
@@ -284,7 +302,6 @@ class ResourceLoader {
// Check $name for validity
if ( !self::isValidModuleName( $name ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "ResourceLoader module name '$name' is invalid, "
. "see ResourceLoader::isValidModuleName()" );
}
@@ -298,7 +315,6 @@ class ResourceLoader {
// 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 ) . ')'
@@ -323,19 +339,16 @@ class ResourceLoader {
} elseif ( isset( $skinStyles['+' . $name] ) ) {
$paths = (array)$skinStyles['+' . $name];
$styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
- $this->moduleInfos[$name]['skinStyles']['default'] :
+ (array)$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.
+ // from the module we're modifying, which come from the base definition.
list( $localBasePath, $remoteBasePath ) =
ResourceLoaderFileModule::extractBasePaths( $skinStyles );
- list( $localBasePath, $remoteBasePath ) =
- ResourceLoaderFileModule::extractBasePaths( $paths, $localBasePath, $remoteBasePath );
foreach ( $paths as $path ) {
$styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
@@ -346,7 +359,6 @@ class ResourceLoader {
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -360,13 +372,11 @@ class ResourceLoader {
. 'Edit your <code>LocalSettings.php</code> to enable it.' );
}
- wfProfileIn( __METHOD__ );
-
// Get core test suites
$testModules = array();
$testModules['qunit'] = array();
// Get other test suites (e.g. from extensions)
- wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
+ Hooks::run( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
// Add the testrunner (which configures QUnit) to the dependencies.
// Since it must be ready before any of the test suites are executed.
@@ -389,7 +399,6 @@ class ResourceLoader {
$this->testModuleNames[$id] = array_keys( $testModules[$id] );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -464,6 +473,17 @@ class ResourceLoader {
}
/**
+ * Check whether a ResourceLoader module is registered
+ *
+ * @since 1.25
+ * @param string $name
+ * @return bool
+ */
+ public function isModuleRegistered( $name ) {
+ return isset( $this->moduleInfos[$name] );
+ }
+
+ /**
* Get the ResourceLoaderModule object for a given module name.
*
* If an array of module parameters exists but a ResourceLoaderModule object has not
@@ -568,9 +588,6 @@ class ResourceLoader {
// See http://bugs.php.net/bug.php?id=36514
ob_start();
- wfProfileIn( __METHOD__ );
- $errors = '';
-
// Find out which modules are missing and instantiate the others
$modules = array();
$missing = array();
@@ -581,10 +598,7 @@ class ResourceLoader {
// This is a security issue, see bug 34907.
if ( $module->getGroup() === 'private' ) {
wfDebugLog( 'resourceloader', __METHOD__ . ": request for private module '$name' denied" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $errors .= self::makeComment( "Cannot show private module \"$name\"" );
-
+ $this->errors[] = "Cannot show private module \"$name\"";
continue;
}
$modules[$name] = $module;
@@ -599,13 +613,9 @@ class ResourceLoader {
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": preloading module info failed: $e" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $errors .= self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
- wfProfileIn( __METHOD__ . '-getModifiedTime' );
-
// To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
$mtime = wfTimestamp( TS_UNIX, $this->config->get( 'CacheEpoch' ) );
@@ -619,36 +629,27 @@ class ResourceLoader {
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": calculating maximum modified time failed: $e" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $errors .= self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
}
- wfProfileOut( __METHOD__ . '-getModifiedTime' );
-
// If there's an If-Modified-Since header, respond with a 304 appropriately
if ( $this->tryRespondLastModified( $context, $mtime ) ) {
- wfProfileOut( __METHOD__ );
return; // output handled (buffers cleared)
}
// Generate a response
$response = $this->makeModuleResponse( $context, $modules, $missing );
- // Prepend comments indicating exceptions
- $response = $errors . $response;
-
// Capture any PHP warnings from the output buffer and append them to the
- // response in a comment if we're in debug mode.
+ // error list if we're in debug mode.
if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
- $response = self::makeComment( $warnings ) . $response;
- $this->hasErrors = true;
+ $this->errors[] = $warnings;
}
// Save response to file cache unless there are errors
- if ( isset( $fileCache ) && !$errors && !count( $missing ) ) {
- // Cache single modules...and other requests if there are enough hits
+ if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
+ // Cache single modules and images...and other requests if there are enough hits
if ( ResourceFileCache::useFileCache( $context ) ) {
if ( $fileCache->isCacheWorthy() ) {
$fileCache->saveText( $response );
@@ -659,20 +660,37 @@ class ResourceLoader {
}
// Send content type and cache related headers
- $this->sendResponseHeaders( $context, $mtime, $this->hasErrors );
+ $this->sendResponseHeaders( $context, $mtime, (bool)$this->errors );
// Remove the output buffer and output the response
ob_end_clean();
+
+ if ( $context->getImageObj() && $this->errors ) {
+ // We can't show both the error messages and the response when it's an image.
+ $errorText = '';
+ foreach ( $this->errors as $error ) {
+ $errorText .= $error . "\n";
+ }
+ $response = $errorText;
+ } elseif ( $this->errors ) {
+ // Prepend comments indicating errors
+ $errorText = '';
+ foreach ( $this->errors as $error ) {
+ $errorText .= self::makeComment( $error );
+ }
+ $response = $errorText . $response;
+ }
+
+ $this->errors = array();
echo $response;
- wfProfileOut( __METHOD__ );
}
/**
* Send content type and last modified headers to the client.
* @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
+ * @param bool $errors Whether there are errors in the response
* @return void
*/
protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
@@ -689,7 +707,14 @@ class ResourceLoader {
$maxage = $rlMaxage['versioned']['client'];
$smaxage = $rlMaxage['versioned']['server'];
}
- if ( $context->getOnly() === 'styles' ) {
+ if ( $context->getImageObj() ) {
+ // Output different headers if we're outputting textual errors.
+ if ( $errors ) {
+ header( 'Content-Type: text/plain; charset=utf-8' );
+ } else {
+ $context->getImageObj()->sendResponseHeaders( $context );
+ }
+ } elseif ( $context->getOnly() === 'styles' ) {
header( 'Content-Type: text/css; charset=utf-8' );
header( 'Access-Control-Allow-Origin: *' );
} else {
@@ -813,15 +838,26 @@ class ResourceLoader {
* Handle exception display.
*
* @param Exception $e Exception to be shown to the user
- * @return string Sanitized text that can be returned to the user
+ * @return string Sanitized text in a CSS/JS comment that can be returned to the user
*/
public static function formatException( $e ) {
+ return self::makeComment( self::formatExceptionNoComment( $e ) );
+ }
+
+ /**
+ * Handle exception display.
+ *
+ * @since 1.25
+ * @param Exception $e Exception to be shown to the user
+ * @return string Sanitized text that can be returned to the user
+ */
+ protected static function formatExceptionNoComment( $e ) {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
- return self::makeComment( $e->__toString() );
+ return $e->__toString();
} else {
- return self::makeComment( wfMessage( 'internalerror' )->text() );
+ return wfMessage( 'internalerror' )->text();
}
}
@@ -837,30 +873,37 @@ class ResourceLoader {
array $modules, array $missing = array()
) {
$out = '';
- $exceptions = '';
$states = array();
if ( !count( $modules ) && !count( $missing ) ) {
- return "/* This file is the Web entry point for MediaWiki's ResourceLoader:
+ return <<<MESSAGE
+/* 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. */";
+ no modules were requested. Max made me put this here. */
+MESSAGE;
}
- wfProfileIn( __METHOD__ );
+ $image = $context->getImageObj();
+ if ( $image ) {
+ $data = $image->getImageData( $context );
+ if ( $data === false ) {
+ $data = '';
+ $this->errors[] = 'Image generation failed';
+ }
+ return $data;
+ }
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
try {
- $blobs = MessageBlobStore::getInstance()->get( $this, $modules, $context->getLanguage() );
+ $blobs = $this->blobStore->get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
MWExceptionHandler::logException( $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 );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
} else {
$blobs = array();
@@ -877,7 +920,6 @@ class ResourceLoader {
* @var $module ResourceLoaderModule
*/
- wfProfileIn( __METHOD__ . '-' . $name );
try {
$scripts = '';
if ( $context->shouldIncludeScripts() ) {
@@ -964,28 +1006,33 @@ class ResourceLoader {
case 'messages':
$out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
break;
+ case 'templates':
+ $out .= Xml::encodeJsCall(
+ 'mw.templates.set',
+ array( $name, (object)$module->getTemplates() ),
+ ResourceLoader::inDebugMode()
+ );
+ break;
default:
$out .= self::makeLoaderImplementScript(
$name,
$scripts,
$styles,
- new XmlJsCode( $messagesBlob )
+ new XmlJsCode( $messagesBlob ),
+ $module->getTemplates()
);
break;
}
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": generating module package failed: $e" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $exceptions .= self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
// Respond to client with error-state instead of module implementation
$states[$name] = 'error';
unset( $modules[$name] );
}
$isRaw |= $module->isRaw();
- wfProfileOut( __METHOD__ . '-' . $name );
}
// Update module states
@@ -1004,9 +1051,8 @@ class ResourceLoader {
}
} else {
if ( count( $states ) ) {
- $exceptions .= self::makeComment(
- 'Problematic modules: ' . FormatJson::encode( $states, ResourceLoader::inDebugMode() )
- );
+ $this->errors[] = 'Problematic modules: ' .
+ FormatJson::encode( $states, ResourceLoader::inDebugMode() );
}
}
@@ -1018,8 +1064,7 @@ class ResourceLoader {
}
}
- wfProfileOut( __METHOD__ );
- return $exceptions . $out;
+ return $out;
}
/* Static Methods */
@@ -1034,30 +1079,32 @@ class ResourceLoader {
* @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.
+ * @param array $templates Keys are name of templates and values are the source of
+ * the template.
* @throws MWException
* @return string
*/
- public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
+ public static function makeLoaderImplementScript( $name, $scripts, $styles,
+ $messages, $templates
+ ) {
if ( is_string( $scripts ) ) {
$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.' );
}
- return Xml::encodeJsCall(
- 'mw.loader.implement',
- array(
- $name,
- $scripts,
- // Force objects. mw.loader.implement requires them to be javascript objects.
- // Although these variables are associative arrays, which become javascript
- // objects through json_encode. In many cases they will be empty arrays, and
- // PHP/json_encode() consider empty arrays to be numerical arrays and
- // output javascript "[]" instead of "{}". This fixes that.
- (object)$styles,
- (object)$messages
- ),
- ResourceLoader::inDebugMode()
+ // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
+ // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
+ // of "{}". Force them to objects.
+ $module = array(
+ $name,
+ $scripts,
+ (object) $styles,
+ (object) $messages,
+ (object) $templates,
);
+ self::trimArray( $module );
+
+ return Xml::encodeJsCall( 'mw.loader.implement', $module, ResourceLoader::inDebugMode() );
}
/**
@@ -1164,6 +1211,40 @@ class ResourceLoader {
);
}
+ private static function isEmptyObject( stdClass $obj ) {
+ foreach ( $obj as $key => &$value ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove empty values from the end of an array.
+ *
+ * Values considered empty:
+ *
+ * - null
+ * - array()
+ * - new XmlJsCode( '{}' )
+ * - new stdClass() // (object) array()
+ *
+ * @param Array $array
+ */
+ private static function trimArray( Array &$array ) {
+ $i = count( $array );
+ while ( $i-- ) {
+ if ( $array[$i] === null
+ || $array[$i] === array()
+ || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
+ || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
+ ) {
+ unset( $array[$i] );
+ } else {
+ break;
+ }
+ }
+ }
+
/**
* Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
@@ -1195,16 +1276,37 @@ class ResourceLoader {
$dependencies = null, $group = null, $source = null, $skip = null
) {
if ( is_array( $name ) ) {
+ // Build module name index
+ $index = array();
+ foreach ( $name as $i => &$module ) {
+ $index[$module[0]] = $i;
+ }
+
+ // Transform dependency names into indexes when possible, they will be resolved by
+ // mw.loader.register on the other end
+ foreach ( $name as &$module ) {
+ if ( isset( $module[2] ) ) {
+ foreach ( $module[2] as &$dependency ) {
+ if ( isset( $index[$dependency] ) ) {
+ $dependency = $index[$dependency];
+ }
+ }
+ }
+ }
+
+ array_walk( $name, array( 'self', 'trimArray' ) );
+
return Xml::encodeJsCall(
'mw.loader.register',
array( $name ),
ResourceLoader::inDebugMode()
);
} else {
- $version = (int)$version > 1 ? (int)$version : 1;
+ $registration = array( $name, $version, $dependencies, $group, $source, $skip );
+ self::trimArray( $registration );
return Xml::encodeJsCall(
'mw.loader.register',
- array( $name, $version, $dependencies, $group, $source, $skip ),
+ $registration,
ResourceLoader::inDebugMode()
);
}
@@ -1466,6 +1568,9 @@ class ResourceLoader {
// 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.
+ if ( !class_exists( 'lessc' ) ) {
+ throw new MWException( 'MediaWiki requires the lessphp compiler' );
+ }
if ( !function_exists( 'ctype_digit' ) ) {
throw new MWException( 'lessc requires the Ctype extension' );
}
@@ -1488,9 +1593,13 @@ class ResourceLoader {
* @return array Map of variable names to string CSS values.
*/
public static function getLessVars( Config $config ) {
- $lessVars = $config->get( 'ResourceLoaderLESSVars' );
- // Sort by key to ensure consistent hashing for cache lookups.
- ksort( $lessVars );
- return $lessVars;
+ if ( !self::$lessVars ) {
+ $lessVars = $config->get( 'ResourceLoaderLESSVars' );
+ Hooks::run( 'ResourceLoaderGetLessVars', array( &$lessVars ) );
+ // Sort by key to ensure consistent hashing for cache lookups.
+ ksort( $lessVars );
+ self::$lessVars = $lessVars;
+ }
+ return self::$lessVars;
}
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index 7af7b898..a6a7d347 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -41,6 +41,11 @@ class ResourceLoaderContext {
protected $version;
protected $hash;
protected $raw;
+ protected $image;
+ protected $variant;
+ protected $format;
+ protected $userObj;
+ protected $imageObj;
/* Methods */
@@ -65,6 +70,10 @@ class ResourceLoaderContext {
$this->only = $request->getVal( 'only' );
$this->version = $request->getVal( 'version' );
$this->raw = $request->getFuzzyBool( 'raw' );
+ // Image requests
+ $this->image = $request->getVal( 'image' );
+ $this->variant = $request->getVal( 'variant' );
+ $this->format = $request->getVal( 'format' );
$skinnames = Skin::getSkinNames();
// If no skin is specified, or we don't recognize the skin, use the default skin
@@ -179,6 +188,31 @@ class ResourceLoaderContext {
}
/**
+ * Get the possibly-cached User object for the specified username
+ *
+ * @since 1.25
+ * @return User|bool false if a valid object cannot be created
+ */
+ public function getUserObj() {
+ if ( $this->userObj === null ) {
+ $username = $this->getUser();
+ if ( $username ) {
+ // Optimize: Avoid loading a new User object if possible
+ global $wgUser;
+ if ( is_object( $wgUser ) && $wgUser->getName() === $username ) {
+ $this->userObj = $wgUser;
+ } else {
+ $this->userObj = User::newFromName( $username );
+ }
+ } else {
+ $this->userObj = new User; // Anonymous user
+ }
+ }
+
+ return $this->userObj;
+ }
+
+ /**
* @return bool
*/
public function getDebug() {
@@ -207,6 +241,62 @@ class ResourceLoaderContext {
}
/**
+ * @return string|null
+ */
+ public function getImage() {
+ return $this->image;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getVariant() {
+ return $this->variant;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getFormat() {
+ return $this->format;
+ }
+
+ /**
+ * If this is a request for an image, get the ResourceLoaderImage object.
+ *
+ * @since 1.25
+ * @return ResourceLoaderImage|bool false if a valid object cannot be created
+ */
+ public function getImageObj() {
+ if ( $this->imageObj === null ) {
+ $this->imageObj = false;
+
+ if ( !$this->image ) {
+ return $this->imageObj;
+ }
+
+ $modules = $this->getModules();
+ if ( count( $modules ) !== 1 ) {
+ return $this->imageObj;
+ }
+
+ $module = $this->getResourceLoader()->getModule( $modules[0] );
+ if ( !$module || !$module instanceof ResourceLoaderImageModule ) {
+ return $this->imageObj;
+ }
+
+ $image = $module->getImage( $this->image );
+ if ( !$image ) {
+ return $this->imageObj;
+ }
+
+ $this->imageObj = $image;
+ }
+
+ return $this->imageObj;
+ }
+
+ /**
* @return bool
*/
public function shouldIncludeScripts() {
@@ -234,6 +324,7 @@ class ResourceLoaderContext {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
$this->getLanguage(), $this->getDirection(), $this->getSkin(), $this->getUser(),
+ $this->getImage(), $this->getVariant(), $this->getFormat(),
$this->getDebug(), $this->getOnly(), $this->getVersion()
) );
}
diff --git a/includes/resourceloader/ResourceLoaderEditToolbarModule.php b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
index 2e07911c..d79174cd 100644
--- a/includes/resourceloader/ResourceLoaderEditToolbarModule.php
+++ b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
@@ -32,6 +32,7 @@ class ResourceLoaderEditToolbarModule extends ResourceLoaderFileModule {
*
* @param string $value
* @return string
+ * @throws Exception
*/
private static function cssSerializeString( $value ) {
if ( strstr( $value, "\0" ) ) {
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index dc8b14a2..671098e1 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -34,6 +34,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/** @var string Remote base path, see __construct() */
protected $remoteBasePath = '';
+ /** @var array Saves a list of the templates named by the modules. */
+ protected $templates = array();
+
/**
* @var array List of paths to JavaScript files to always include
* @par Usage:
@@ -171,7 +174,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* to $wgResourceBasePath
*
* Below is a description for the $options array:
- * @throws MWException
+ * @throws InvalidArgumentException
* @par Construction options:
* @code
* array(
@@ -199,6 +202,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* 'loaderScripts' => [file path string or array of file path strings],
* // Modules which must be loaded before this module
* 'dependencies' => [module name string or array of module name strings],
+ * 'templates' => array(
+ * [template alias with file.ext] => [file path to a template file],
+ * ),
* // Styles to always load
* 'styles' => [file path string or array of file path strings],
* // Styles to include in specific skin contexts
@@ -223,6 +229,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$localBasePath = null,
$remoteBasePath = null
) {
+ // Flag to decide whether to automagically add the mediawiki.template module
+ $hasTemplates = false;
// localBasePath and remoteBasePath both have unbelievably long fallback chains
// and need to be handled separately.
list( $this->localBasePath, $this->remoteBasePath ) =
@@ -238,19 +246,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'styles':
$this->{$member} = (array)$option;
break;
+ case 'templates':
+ $hasTemplates = true;
+ $this->{$member} = (array)$option;
+ break;
// Collated lists of file paths
case 'languageScripts':
case 'skinScripts':
case 'skinStyles':
if ( !is_array( $option ) ) {
- throw new MWException(
+ throw new InvalidArgumentException(
"Invalid collated file path list error. " .
"'$option' given, array expected."
);
}
foreach ( $option as $key => $value ) {
if ( !is_string( $key ) ) {
- throw new MWException(
+ throw new InvalidArgumentException(
"Invalid collated file path list key error. " .
"'$key' given, string expected."
);
@@ -281,6 +293,21 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
}
}
+ if ( $hasTemplates ) {
+ $this->dependencies[] = 'mediawiki.template';
+ // Ensure relevant template compiler module gets loaded
+ foreach ( $this->templates as $alias => $templatePath ) {
+ if ( is_int( $alias ) ) {
+ $alias = $templatePath;
+ }
+ $suffix = explode( '.', $alias );
+ $suffix = end( $suffix );
+ $compilerModule = 'mediawiki.template.' . $suffix;
+ if ( $suffix !== 'html' && !in_array( $compilerModule, $this->dependencies ) ) {
+ $this->dependencies[] = $compilerModule;
+ }
+ }
+ }
}
/**
@@ -304,7 +331,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
// 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 ( $localBasePath === null ) {
+ $localBasePath = $IP;
+ }
if ( $remoteBasePath === null ) {
$remoteBasePath = $wgResourceBasePath;
}
@@ -466,8 +495,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Get the skip function.
- *
- * @return string|null
+ * @return null|string
+ * @throws MWException
*/
public function getSkipFunction() {
if ( !$this->skipFunction ) {
@@ -510,7 +539,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
return $this->modifiedTime[$context->getHash()];
}
- wfProfileIn( __METHOD__ );
$files = array();
@@ -533,8 +561,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$files = array_merge(
$files,
$this->scripts,
+ $this->templates,
$context->getDebug() ? $this->debugScripts : array(),
- self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ $this->getLanguageScripts( $context->getLanguage() ),
self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
$this->loaderScripts
);
@@ -544,18 +573,19 @@ 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() ) );
+ // Filter out any duplicates from getFileDependencies() and others.
+ // Most commonly introduced by compileLessFile(), which always includes the
+ // entry point Less file we already know about.
+ $files = array_values( array_unique( $files ) );
// 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()];
}
- wfProfileIn( __METHOD__ . '-filemtime' );
$filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
- wfProfileOut( __METHOD__ . '-filemtime' );
$this->modifiedTime[$context->getHash()] = max(
$filesMtime,
@@ -563,7 +593,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$this->getDefinitionMtime( $context )
);
- wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()];
}
@@ -574,9 +603,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @return array
*/
public function getDefinitionSummary( ResourceLoaderContext $context ) {
- $summary = array(
- 'class' => get_class( $this ),
- );
+ $summary = parent::getDefinitionSummary( $context );
foreach ( array(
'scripts',
'debugScripts',
@@ -588,6 +615,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
'dependencies',
'messages',
'targets',
+ 'templates',
'group',
'position',
'skipFunction',
@@ -698,7 +726,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected function getScriptFiles( ResourceLoaderContext $context ) {
$files = array_merge(
$this->scripts,
- self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ $this->getLanguageScripts( $context->getLanguage() ),
self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
);
if ( $context->getDebug() ) {
@@ -709,6 +737,29 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * Get the set of language scripts for the given language,
+ * possibly using a fallback language.
+ *
+ * @param string $lang
+ * @return array
+ */
+ private function getLanguageScripts( $lang ) {
+ $scripts = self::tryForKey( $this->languageScripts, $lang );
+ if ( $scripts ) {
+ return $scripts;
+ }
+ $fallbacks = Language::getFallbacksFor( $lang );
+ foreach ( $fallbacks as $lang ) {
+ $scripts = self::tryForKey( $this->languageScripts, $lang );
+ if ( $scripts ) {
+ return $scripts;
+ }
+ }
+
+ return array();
+ }
+
+ /**
* Get a list of file paths for all styles in this module, in order of proper inclusion.
*
* @param ResourceLoaderContext $context
@@ -934,4 +985,30 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected function getLessCompiler( ResourceLoaderContext $context = null ) {
return ResourceLoader::getLessCompiler( $this->getConfig() );
}
+
+ /**
+ * Takes named templates by the module and returns an array mapping.
+ * @return array of templates mapping template alias to content
+ * @throws MWException
+ */
+ public function getTemplates() {
+ $templates = array();
+
+ foreach ( $this->templates as $alias => $templatePath ) {
+ // Alias is optional
+ if ( is_int( $alias ) ) {
+ $alias = $templatePath;
+ }
+ $localPath = $this->getLocalPath( $templatePath );
+ if ( file_exists( $localPath ) ) {
+ $content = file_get_contents( $localPath );
+ $templates[$alias] = $content;
+ } else {
+ $msg = __METHOD__ . ": template file not found: \"$localPath\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ }
+ return $templates;
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/resourceloader/ResourceLoaderFilePageModule.php
deleted file mode 100644
index 8c7fbe76..00000000
--- a/includes/resourceloader/ResourceLoaderFilePageModule.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-/**
- * Resource loader module for MediaWiki:Filepage.css
- *
- * 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 definition for MediaWiki:Filepage.css
- */
-class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
-
- /**
- * @param ResourceLoaderContext $context
- * @return array
- */
- protected function getPages( ResourceLoaderContext $context ) {
- return array(
- 'MediaWiki:Filepage.css' => array( 'type' => 'style' ),
- );
- }
-}
diff --git a/includes/resourceloader/ResourceLoaderImage.php b/includes/resourceloader/ResourceLoaderImage.php
new file mode 100644
index 00000000..12d1e827
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderImage.php
@@ -0,0 +1,388 @@
+<?php
+/**
+ * Class encapsulating an image used in a ResourceLoaderImageModule.
+ *
+ * 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 encapsulating an image used in a ResourceLoaderImageModule.
+ *
+ * @since 1.25
+ */
+class ResourceLoaderImage {
+
+ /**
+ * Map of allowed file extensions to their MIME types.
+ * @var array
+ */
+ protected static $fileTypes = array(
+ 'svg' => 'image/svg+xml',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'jpg' => 'image/jpg',
+ );
+
+ /**
+ * @param string $name Image name
+ * @param string $module Module name
+ * @param string|array $descriptor Path to image file, or array structure containing paths
+ * @param string $basePath Directory to which paths in descriptor refer
+ * @param array $variants
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $name, $module, $descriptor, $basePath, $variants ) {
+ $this->name = $name;
+ $this->module = $module;
+ $this->descriptor = $descriptor;
+ $this->basePath = $basePath;
+ $this->variants = $variants;
+
+ // Expand shorthands:
+ // array( "en,de,fr" => "foo.svg" ) → array( "en" => "foo.svg", "de" => "foo.svg", "fr" => "foo.svg" )
+ if ( is_array( $this->descriptor ) && isset( $this->descriptor['lang'] ) ) {
+ foreach ( array_keys( $this->descriptor['lang'] ) as $langList ) {
+ if ( strpos( $langList, ',' ) !== false ) {
+ $this->descriptor['lang'] += array_fill_keys(
+ explode( ',', $langList ),
+ $this->descriptor['lang'][ $langList ]
+ );
+ unset( $this->descriptor['lang'][ $langList ] );
+ }
+ }
+ }
+
+ // Ensure that all files have common extension.
+ $extensions = array();
+ $descriptor = (array)$descriptor;
+ array_walk_recursive( $descriptor, function ( $path ) use ( &$extensions ) {
+ $extensions[] = pathinfo( $path, PATHINFO_EXTENSION );
+ } );
+ $extensions = array_unique( $extensions );
+ if ( count( $extensions ) !== 1 ) {
+ throw new InvalidArgumentException( "File type for different image files of '$name' not the same" );
+ }
+ $ext = $extensions[0];
+ if ( !isset( self::$fileTypes[$ext] ) ) {
+ throw new InvalidArgumentException( "Invalid file type for image files of '$name' (valid: svg, png, gif, jpg)" );
+ }
+ $this->extension = $ext;
+ }
+
+ /**
+ * Get name of this image.
+ *
+ * @return string
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Get name of the module this image belongs to.
+ *
+ * @return string
+ */
+ public function getModule() {
+ return $this->module;
+ }
+
+ /**
+ * Get the list of variants this image can be converted to.
+ *
+ * @return string[]
+ */
+ public function getVariants() {
+ return array_keys( $this->variants );
+ }
+
+ /**
+ * Get the path to image file for given context.
+ *
+ * @param ResourceLoaderContext $context Any context
+ * @return string
+ */
+ protected function getPath( ResourceLoaderContext $context ) {
+ $desc = $this->descriptor;
+ if ( is_string( $desc ) ) {
+ return $this->basePath . '/' . $desc;
+ } elseif ( isset( $desc['lang'][ $context->getLanguage() ] ) ) {
+ return $this->basePath . '/' . $desc['lang'][ $context->getLanguage() ];
+ } elseif ( isset( $desc[ $context->getDirection() ] ) ) {
+ return $this->basePath . '/' . $desc[ $context->getDirection() ];
+ } else {
+ return $this->basePath . '/' . $desc['default'];
+ }
+ }
+
+ /**
+ * Get the extension of the image.
+ *
+ * @param string $format Format to get the extension for, 'original' or 'rasterized'
+ * @return string Extension without leading dot, e.g. 'png'
+ */
+ public function getExtension( $format = 'original' ) {
+ if ( $format === 'rasterized' && $this->extension === 'svg' ) {
+ return 'png';
+ } else {
+ return $this->extension;
+ }
+ }
+
+ /**
+ * Get the MIME type of the image.
+ *
+ * @param string $format Format to get the MIME type for, 'original' or 'rasterized'
+ * @return string
+ */
+ public function getMimeType( $format = 'original' ) {
+ $ext = $this->getExtension( $format );
+ return self::$fileTypes[$ext];
+ }
+
+ /**
+ * Get the load.php URL that will produce this image.
+ *
+ * @param ResourceLoaderContext $context Any context
+ * @param string $script URL to load.php
+ * @param string|null $variant Variant to get the URL for
+ * @param string $format Format to get the URL for, 'original' or 'rasterized'
+ * @return string
+ */
+ public function getUrl( ResourceLoaderContext $context, $script, $variant, $format ) {
+ $query = array(
+ 'modules' => $this->getModule(),
+ 'image' => $this->getName(),
+ 'variant' => $variant,
+ 'format' => $format,
+ 'lang' => $context->getLanguage(),
+ 'version' => $context->getVersion(),
+ );
+
+ return wfExpandUrl( wfAppendQuery( $script, $query ), PROTO_RELATIVE );
+ }
+
+ /**
+ * Get the data: URI that will produce this image.
+ *
+ * @param ResourceLoaderContext $context Any context
+ * @param string|null $variant Variant to get the URI for
+ * @param string $format Format to get the URI for, 'original' or 'rasterized'
+ * @return string
+ */
+ public function getDataUri( ResourceLoaderContext $context, $variant, $format ) {
+ $type = $this->getMimeType( $format );
+ $contents = $this->getImageData( $context, $variant, $format );
+ return CSSMin::encodeStringAsDataURI( $contents, $type );
+ }
+
+ /**
+ * Get actual image data for this image. This can be saved to a file or sent to the browser to
+ * produce the converted image.
+ *
+ * Call getExtension() or getMimeType() with the same $format argument to learn what file type the
+ * returned data uses.
+ *
+ * @param ResourceLoaderContext $context Image context, or any context if $variant and $format
+ * given.
+ * @param string|null $variant Variant to get the data for. Optional; if given, overrides the data
+ * from $context.
+ * @param string $format Format to get the data for, 'original' or 'rasterized'. Optional; if
+ * given, overrides the data from $context.
+ * @return string|false Possibly binary image data, or false on failure
+ * @throws MWException If the image file doesn't exist
+ */
+ public function getImageData( ResourceLoaderContext $context, $variant = false, $format = false ) {
+ if ( $variant === false ) {
+ $variant = $context->getVariant();
+ }
+ if ( $format === false ) {
+ $format = $context->getFormat();
+ }
+
+ $path = $this->getPath( $context );
+ if ( !file_exists( $path ) ) {
+ throw new MWException( "File '$path' does not exist" );
+ }
+
+ if ( $this->getExtension() !== 'svg' ) {
+ return file_get_contents( $path );
+ }
+
+ if ( $variant && isset( $this->variants[$variant] ) ) {
+ $data = $this->variantize( $this->variants[$variant], $context );
+ } else {
+ $data = file_get_contents( $path );
+ }
+
+ if ( $format === 'rasterized' ) {
+ $data = $this->rasterize( $data );
+ if ( !$data ) {
+ wfDebugLog( 'ResourceLoaderImage', __METHOD__ . " failed to rasterize for $path" );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Send response headers (using the header() function) that are necessary to correctly serve the
+ * image data for this image, as returned by getImageData().
+ *
+ * Note that the headers are independent of the language or image variant.
+ *
+ * @param ResourceLoaderContext $context Image context
+ */
+ public function sendResponseHeaders( ResourceLoaderContext $context ) {
+ $format = $context->getFormat();
+ $mime = $this->getMimeType( $format );
+ $filename = $this->getName() . '.' . $this->getExtension( $format );
+
+ header( 'Content-Type: ' . $mime );
+ header( 'Content-Disposition: ' .
+ FileBackend::makeContentDisposition( 'inline', $filename ) );
+ }
+
+ /**
+ * Convert this image, which is assumed to be SVG, to given variant.
+ *
+ * @param array $variantConf Array with a 'color' key, its value will be used as fill color
+ * @param ResourceLoaderContext $context Image context
+ * @return string New SVG file data
+ */
+ protected function variantize( $variantConf, ResourceLoaderContext $context ) {
+ $dom = new DomDocument;
+ $dom->load( $this->getPath( $context ) );
+ $root = $dom->documentElement;
+ $wrapper = $dom->createElement( 'g' );
+ while ( $root->firstChild ) {
+ $wrapper->appendChild( $root->firstChild );
+ }
+ $root->appendChild( $wrapper );
+ $wrapper->setAttribute( 'fill', $variantConf['color'] );
+ return $dom->saveXml();
+ }
+
+ /**
+ * Massage the SVG image data for converters which don't understand some path data syntax.
+ *
+ * This is necessary for rsvg and ImageMagick when compiled with rsvg support.
+ * Upstream bug is https://bugzilla.gnome.org/show_bug.cgi?id=620923, fixed 2014-11-10, so
+ * this will be needed for a while. (T76852)
+ *
+ * @param string $svg SVG image data
+ * @return string Massaged SVG image data
+ */
+ protected function massageSvgPathdata( $svg ) {
+ $dom = new DomDocument;
+ $dom->loadXml( $svg );
+ foreach ( $dom->getElementsByTagName( 'path' ) as $node ) {
+ $pathData = $node->getAttribute( 'd' );
+ // Make sure there is at least one space between numbers, and that leading zero is not omitted.
+ // rsvg has issues with syntax like "M-1-2" and "M.445.483" and especially "M-.445-.483".
+ $pathData = preg_replace( '/(-?)(\d*\.\d+|\d+)/', ' ${1}0$2 ', $pathData );
+ // Strip unnecessary leading zeroes for prettiness, not strictly necessary
+ $pathData = preg_replace( '/([ -])0(\d)/', '$1$2', $pathData );
+ $node->setAttribute( 'd', $pathData );
+ }
+ return $dom->saveXml();
+ }
+
+ /**
+ * Convert passed image data, which is assumed to be SVG, to PNG.
+ *
+ * @param string $svg SVG image data
+ * @return string|bool PNG image data, or false on failure
+ */
+ protected function rasterize( $svg ) {
+ // This code should be factored out to a separate method on SvgHandler, or perhaps a separate
+ // class, with a separate set of configuration settings.
+ //
+ // This is a distinct use case from regular SVG rasterization:
+ // * We can skip many sanity and security checks (as the images come from a trusted source,
+ // rather than from the user).
+ // * We need to provide extra options to some converters to achieve acceptable quality for very
+ // small images, which might cause performance issues in the general case.
+ // * We want to directly pass image data to the converter, rather than a file path.
+ //
+ // See https://phabricator.wikimedia.org/T76473#801446 for examples of what happens with the
+ // default settings.
+ //
+ // For now, we special-case rsvg (used in WMF production) and do a messy workaround for other
+ // converters.
+
+ global $wgSVGConverter, $wgSVGConverterPath;
+
+ $svg = $this->massageSvgPathdata( $svg );
+
+ // Sometimes this might be 'rsvg-secure'. Long as it's rsvg.
+ if ( strpos( $wgSVGConverter, 'rsvg' ) === 0 ) {
+ $command = 'rsvg-convert';
+ if ( $wgSVGConverterPath ) {
+ $command = wfEscapeShellArg( "$wgSVGConverterPath/" ) . $command;
+ }
+
+ $process = proc_open(
+ $command,
+ array( 0 => array( 'pipe', 'r' ), 1 => array( 'pipe', 'w' ) ),
+ $pipes
+ );
+
+ if ( is_resource( $process ) ) {
+ fwrite( $pipes[0], $svg );
+ fclose( $pipes[0] );
+ $png = stream_get_contents( $pipes[1] );
+ fclose( $pipes[1] );
+ proc_close( $process );
+
+ return $png ?: false;
+ }
+ return false;
+
+ } else {
+ // Write input to and read output from a temporary file
+ $tempFilenameSvg = tempnam( wfTempDir(), 'ResourceLoaderImage' );
+ $tempFilenamePng = tempnam( wfTempDir(), 'ResourceLoaderImage' );
+
+ file_put_contents( $tempFilenameSvg, $svg );
+
+ $metadata = SVGMetadataExtractor::getMetadata( $tempFilenameSvg );
+ if ( !isset( $metadata['width'] ) || !isset( $metadata['height'] ) ) {
+ unlink( $tempFilenameSvg );
+ return false;
+ }
+
+ $handler = new SvgHandler;
+ $res = $handler->rasterize(
+ $tempFilenameSvg,
+ $tempFilenamePng,
+ $metadata['width'],
+ $metadata['height']
+ );
+ unlink( $tempFilenameSvg );
+
+ $png = null;
+ if ( $res === true ) {
+ $png = file_get_contents( $tempFilenamePng );
+ unlink( $tempFilenamePng );
+ }
+
+ return $png ?: false;
+ }
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderImageModule.php b/includes/resourceloader/ResourceLoaderImageModule.php
new file mode 100644
index 00000000..bf6a7dd2
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderImageModule.php
@@ -0,0 +1,327 @@
+<?php
+/**
+ * Resource loader module for generated and embedded images.
+ *
+ * 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
+ */
+
+/**
+ * Resource loader module for generated and embedded images.
+ *
+ * @since 1.25
+ */
+class ResourceLoaderImageModule extends ResourceLoaderModule {
+
+ /**
+ * Local base path, see __construct()
+ * @var string
+ */
+ protected $localBasePath = '';
+
+ protected $origin = self::ORIGIN_CORE_SITEWIDE;
+
+ protected $images = array();
+ protected $variants = array();
+ protected $prefix = null;
+ protected $selectorWithoutVariant = '.{prefix}-{name}';
+ protected $selectorWithVariant = '.{prefix}-{name}-{variant}';
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /**
+ * Constructs a new module from an options array.
+ *
+ * @param array $options List of options; if not given or empty, an empty module will be
+ * constructed
+ * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
+ * to $IP
+ *
+ * Below is a description for the $options array:
+ * @par Construction options:
+ * @code
+ * array(
+ * // Base path to prepend to all local paths in $options. Defaults to $IP
+ * 'localBasePath' => [base path],
+ * // CSS class prefix to use in all style rules
+ * 'prefix' => [CSS class prefix],
+ * // Alternatively: Format of CSS selector to use in all style rules
+ * 'selector' => [CSS selector template, variables: {prefix} {name} {variant}],
+ * // Alternatively: When using variants
+ * 'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {name}],
+ * 'selectorWithVariant' => [CSS selector template, variables: {prefix} {name} {variant}],
+ * // List of variants that may be used for the image files
+ * 'variants' => array(
+ * [variant name] => array(
+ * 'color' => [color string, e.g. '#ffff00'],
+ * 'global' => [boolean, if true, this variant is available
+ * for all images of this type],
+ * ),
+ * ...
+ * ),
+ * // List of image files and their options
+ * 'images' => array(
+ * [file path string],
+ * [file path string] => array(
+ * 'name' => [image name string, defaults to file name],
+ * 'variants' => [array of variant name strings, variants
+ * available for this image],
+ * ),
+ * ...
+ * ),
+ * )
+ * @endcode
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $options = array(), $localBasePath = null ) {
+ $this->localBasePath = self::extractLocalBasePath( $options, $localBasePath );
+
+ // Accepted combinations:
+ // * prefix
+ // * selector
+ // * selectorWithoutVariant + selectorWithVariant
+ // * prefix + selector
+ // * prefix + selectorWithoutVariant + selectorWithVariant
+
+ $prefix = isset( $options['prefix'] ) && $options['prefix'];
+ $selector = isset( $options['selector'] ) && $options['selector'];
+ $selectorWithoutVariant = isset( $options['selectorWithoutVariant'] ) && $options['selectorWithoutVariant'];
+ $selectorWithVariant = isset( $options['selectorWithVariant'] ) && $options['selectorWithVariant'];
+
+ if ( $selectorWithoutVariant && !$selectorWithVariant ) {
+ throw new InvalidArgumentException( "Given 'selectorWithoutVariant' but no 'selectorWithVariant'." );
+ }
+ if ( $selectorWithVariant && !$selectorWithoutVariant ) {
+ throw new InvalidArgumentException( "Given 'selectorWithVariant' but no 'selectorWithoutVariant'." );
+ }
+ if ( $selector && $selectorWithVariant ) {
+ throw new InvalidArgumentException( "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given." );
+ }
+ if ( !$prefix && !$selector && !$selectorWithVariant ) {
+ throw new InvalidArgumentException( "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given." );
+ }
+
+ foreach ( $options as $member => $option ) {
+ switch ( $member ) {
+ case 'images':
+ case 'variants':
+ if ( !is_array( $option ) ) {
+ throw new InvalidArgumentException(
+ "Invalid list error. '$option' given, array expected."
+ );
+ }
+ $this->{$member} = $option;
+ break;
+
+ case 'prefix':
+ case 'selectorWithoutVariant':
+ case 'selectorWithVariant':
+ $this->{$member} = (string)$option;
+ break;
+
+ case 'selector':
+ $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option;
+ }
+ }
+ }
+
+ /**
+ * Get CSS class prefix used by this module.
+ * @return string
+ */
+ public function getPrefix() {
+ return $this->prefix;
+ }
+
+ /**
+ * Get CSS selector templates used by this module.
+ * @return string
+ */
+ public function getSelectors() {
+ return array(
+ 'selectorWithoutVariant' => $this->selectorWithoutVariant,
+ 'selectorWithVariant' => $this->selectorWithVariant,
+ );
+ }
+
+ /**
+ * Get a ResourceLoaderImage object for given image.
+ * @param string $name Image name
+ * @return ResourceLoaderImage|null
+ */
+ public function getImage( $name ) {
+ $images = $this->getImages();
+ return isset( $images[$name] ) ? $images[$name] : null;
+ }
+
+ /**
+ * Get ResourceLoaderImage objects for all images.
+ * @return ResourceLoaderImage[] Array keyed by image name
+ */
+ public function getImages() {
+ if ( !isset( $this->imageObjects ) ) {
+ $this->imageObjects = array();
+
+ foreach ( $this->images as $name => $options ) {
+ $fileDescriptor = is_string( $options ) ? $options : $options['file'];
+
+ $allowedVariants = array_merge(
+ is_array( $options ) && isset( $options['variants'] ) ? $options['variants'] : array(),
+ $this->getGlobalVariants()
+ );
+ if ( isset( $this->variants ) ) {
+ $variantConfig = array_intersect_key(
+ $this->variants,
+ array_fill_keys( $allowedVariants, true )
+ );
+ } else {
+ $variantConfig = array();
+ }
+
+ $image = new ResourceLoaderImage(
+ $name,
+ $this->getName(),
+ $fileDescriptor,
+ $this->localBasePath,
+ $variantConfig
+ );
+ $this->imageObjects[ $image->getName() ] = $image;
+ }
+ }
+
+ return $this->imageObjects;
+ }
+
+ /**
+ * Get list of variants in this module that are 'global', i.e., available
+ * for every image regardless of image options.
+ * @return string[]
+ */
+ public function getGlobalVariants() {
+ if ( !isset( $this->globalVariants ) ) {
+ $this->globalVariants = array();
+
+ if ( isset( $this->variants ) ) {
+ foreach ( $this->variants as $name => $config ) {
+ if ( isset( $config['global'] ) && $config['global'] ) {
+ $this->globalVariants[] = $name;
+ }
+ }
+ }
+ }
+
+ return $this->globalVariants;
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ // Build CSS rules
+ $rules = array();
+ $script = $context->getResourceLoader()->getLoadScript( $this->getSource() );
+ $selectors = $this->getSelectors();
+
+ foreach ( $this->getImages() as $name => $image ) {
+ $declarations = $this->getCssDeclarations(
+ $image->getDataUri( $context, null, 'original' ),
+ $image->getUrl( $context, $script, null, 'rasterized' )
+ );
+ $declarations = implode( "\n\t", $declarations );
+ $selector = strtr(
+ $selectors['selectorWithoutVariant'],
+ array(
+ '{prefix}' => $this->getPrefix(),
+ '{name}' => $name,
+ '{variant}' => '',
+ )
+ );
+ $rules[] = "$selector {\n\t$declarations\n}";
+
+ foreach ( $image->getVariants() as $variant ) {
+ $declarations = $this->getCssDeclarations(
+ $image->getDataUri( $context, $variant, 'original' ),
+ $image->getUrl( $context, $script, $variant, 'rasterized' )
+ );
+ $declarations = implode( "\n\t", $declarations );
+ $selector = strtr(
+ $selectors['selectorWithVariant'],
+ array(
+ '{prefix}' => $this->getPrefix(),
+ '{name}' => $name,
+ '{variant}' => $variant,
+ )
+ );
+ $rules[] = "$selector {\n\t$declarations\n}";
+ }
+ }
+
+ $style = implode( "\n", $rules );
+ return array( 'all' => $style );
+ }
+
+ /**
+ * SVG support using a transparent gradient to guarantee cross-browser
+ * compatibility (browsers able to understand gradient syntax support also SVG).
+ * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique
+ *
+ * Keep synchronized with the .background-image-svg LESS mixin in
+ * /resources/src/mediawiki.less/mediawiki.mixins.less.
+ *
+ * @param string $primary Primary URI
+ * @param string $fallback Fallback URI
+ * @return string[] CSS declarations to use given URIs as background-image
+ */
+ protected function getCssDeclarations( $primary, $fallback ) {
+ return array(
+ "background-image: url($fallback);",
+ "background-image: -webkit-linear-gradient(transparent, transparent), url($primary);",
+ "background-image: linear-gradient(transparent, transparent), url($primary);",
+ "background-image: -o-linear-gradient(transparent, transparent), url($fallback);",
+ );
+ }
+
+ /**
+ * @return bool
+ */
+ public function supportsURLLoading() {
+ return false;
+ }
+
+ /**
+ * Extract a local base path from module definition information.
+ *
+ * @param array $options Module definition
+ * @param string $localBasePath Path to use if not provided in module definition. Defaults
+ * to $IP
+ * @return string Local base path
+ */
+ public static function extractLocalBasePath( $options, $localBasePath = null ) {
+ global $IP;
+
+ if ( $localBasePath === null ) {
+ $localBasePath = $IP;
+ }
+
+ if ( array_key_exists( 'localBasePath', $options ) ) {
+ $localBasePath = (string)$options['localBasePath'];
+ }
+
+ return $localBasePath;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
index 09d90d6e..12394536 100644
--- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
@@ -52,10 +52,14 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mw.language.setData', array(
- $context->getLanguage(),
- $this->getData( $context )
- ) );
+ return Xml::encodeJsCall(
+ 'mw.language.setData',
+ array(
+ $context->getLanguage(),
+ $this->getData( $context )
+ ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderLanguageNamesModule.php b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
index fe0c8454..55b1f4b1 100644
--- a/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
@@ -49,11 +49,15 @@ class ResourceLoaderLanguageNamesModule extends ResourceLoaderModule {
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mw.language.setData', array(
- $context->getLanguage(),
- 'languageNames',
- $this->getData( $context )
- ) );
+ return Xml::encodeJsCall(
+ 'mw.language.setData',
+ array(
+ $context->getLanguage(),
+ 'languageNames',
+ $this->getData( $context )
+ ),
+ ResourceLoader::inDebugMode()
+ );
}
public function getDependencies() {
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 45eb70f8..ed16521b 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -135,6 +135,16 @@ abstract class ResourceLoaderModule {
}
/**
+ * Takes named templates by the module and returns an array mapping.
+ *
+ * @return array of templates mapping template alias to content
+ */
+ public function getTemplates() {
+ // Stub, override expected.
+ return array();
+ }
+
+ /**
* @return Config
* @since 1.24
*/
@@ -378,12 +388,12 @@ 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
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
if ( !count( $this->getMessages() ) ) {
- return 0;
+ return 1;
}
$dbr = wfGetDB( DB_SLAVE );
@@ -406,7 +416,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 int $mtime UNIX timestamp or 0 if there is no such blob
+ * @param int $mtime UNIX timestamp
*/
public function setMsgBlobMtime( $lang, $mtime ) {
$this->msgBlobMtime[$lang] = $mtime;
@@ -433,7 +443,6 @@ abstract class ResourceLoaderModule {
* @return int UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- // 0 would mean now
return 1;
}
@@ -441,30 +450,34 @@ abstract class ResourceLoaderModule {
* Helper method for calculating when the module's hash (if it has one) changed.
*
* @param ResourceLoaderContext $context
- * @return int UNIX timestamp or 0 if no hash was provided
- * by getModifiedHash()
+ * @return int UNIX timestamp
*/
public function getHashMtime( ResourceLoaderContext $context ) {
$hash = $this->getModifiedHash( $context );
if ( !is_string( $hash ) ) {
- return 0;
+ return 1;
}
+ // 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.
$cache = wfGetCache( CACHE_ANYTHING );
- $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash );
+ $key = wfMemcKey( 'resourceloader', 'hashmtime', $this->getName(), $hash );
$data = $cache->get( $key );
- if ( is_array( $data ) && $data['hash'] === $hash ) {
- // Hash is still the same, re-use the timestamp of when we first saw this hash.
- return $data['timestamp'];
+ if ( is_int( $data ) && $data > 0 ) {
+ // We've seen this hash before, re-use the timestamp of when we first saw it.
+ return $data;
}
- $timestamp = wfTimestamp();
- $cache->set( $key, array(
- 'hash' => $hash,
- 'timestamp' => $timestamp,
- ) );
-
+ $timestamp = time();
+ $cache->set( $key, $timestamp );
return $timestamp;
}
@@ -487,46 +500,29 @@ abstract class ResourceLoaderModule {
* @since 1.23
*
* @param ResourceLoaderContext $context
- * @return int UNIX timestamp or 0 if no definition summary was provided
- * by getDefinitionSummary()
+ * @return int UNIX timestamp
*/
public function getDefinitionMtime( ResourceLoaderContext $context ) {
- wfProfileIn( __METHOD__ );
$summary = $this->getDefinitionSummary( $context );
if ( $summary === null ) {
- wfProfileOut( __METHOD__ );
- return 0;
+ return 1;
}
$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." );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": New definition for module "
+ . "{$this->getName()} in context \"{$context->getHash()}\"" );
$timestamp = time();
$cache->set( $key, $timestamp );
-
- wfProfileOut( __METHOD__ );
return $timestamp;
}
@@ -630,16 +626,13 @@ abstract class ResourceLoaderModule {
* Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist
* but returns 1 instead.
* @param string $filename File name
- * @return int UNIX timestamp, or 1 if the file doesn't exist
+ * @return int UNIX timestamp
*/
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;
- }
+ wfSuppressWarnings();
+ $mtime = filemtime( $filename ) ?: 1;
+ wfRestoreWarnings();
+
+ return $mtime;
}
}
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 1d9721aa..19e0baeb 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -27,13 +27,10 @@
*/
class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
- /* Protected Methods */
-
/**
- * Gets list of pages used by this module
+ * Get list of pages used by this module
*
* @param ResourceLoaderContext $context
- *
* @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
@@ -45,18 +42,16 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
if ( $this->getConfig()->get( 'UseSiteCss' ) ) {
$pages['MediaWiki:Common.css'] = array( 'type' => 'style' );
$pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'] = array( 'type' => 'style' );
+ $pages['MediaWiki:Print.css'] = array( 'type' => 'style', 'media' => 'print' );
}
- $pages['MediaWiki:Print.css'] = array( 'type' => 'style', 'media' => 'print' );
return $pages;
}
- /* Methods */
-
/**
- * Gets group name
+ * Get group name
*
- * @return string Name of group
+ * @return string
*/
public function getGroup() {
return 'site';
diff --git a/includes/resourceloader/ResourceLoaderSkinModule.php b/includes/resourceloader/ResourceLoaderSkinModule.php
new file mode 100644
index 00000000..3ba63e68
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderSkinModule.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Resource loader module for skin stylesheets.
+ *
+ * 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 Timo Tijhof
+ */
+
+class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
+
+ /* Methods */
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ $logo = $this->getConfig()->get( 'Logo' );
+ $logoHD = $this->getConfig()->get( 'LogoHD' );
+ $styles = parent::getStyles( $context );
+ $styles['all'][] = '.mw-wiki-logo { background-image: ' .
+ CSSMin::buildUrlValue( $logo ) .
+ '; }';
+ if ( $logoHD ) {
+ if ( isset( $logoHD['1.5x'] ) ) {
+ $styles[
+ '(-webkit-min-device-pixel-ratio: 1.5), ' .
+ '(min--moz-device-pixel-ratio: 1.5), ' .
+ '(min-resolution: 1.5dppx), ' .
+ '(min-resolution: 144dpi)'
+ ][] = '.mw-wiki-logo { background-image: ' .
+ CSSMin::buildUrlValue( $logoHD['1.5x'] ) .';' .
+ 'background-size: 135px auto; }';
+ }
+ if ( isset( $logoHD['2x'] ) ) {
+ $styles[
+ '(-webkit-min-device-pixel-ratio: 2), ' .
+ '(min--moz-device-pixel-ratio: 2),'.
+ '(min-resolution: 2dppx), ' .
+ '(min-resolution: 192dpi)'
+ ][] = '.mw-wiki-logo { background-image: ' .
+ CSSMin::buildUrlValue( $logoHD['2x'] ) . ';' .
+ 'background-size: 135px auto; }';
+ }
+ }
+ return $styles;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return bool
+ */
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ // Regardless of whether the files are specified, we always
+ // provide mw-wiki-logo styles.
+ return false;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return int|mixed
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ $parentMTime = parent::getModifiedTime( $context );
+ return max( $parentMTime, $this->getHashMtime( $context ) );
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string: Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ $logo = $this->getConfig()->get( 'Logo' );
+ $logoHD = $this->getConfig()->get( 'LogoHD' );
+ return md5( parent::getModifiedHash( $context ) . $logo . json_encode( $logoHD ) );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php b/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php
new file mode 100644
index 00000000..5c917091
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Resource loader module for populating special characters data for some
+ * editing extensions to use.
+ *
+ * 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
+ */
+
+/**
+ * Resource loader module for populating special characters data for some
+ * editing extensions to use.
+ */
+class ResourceLoaderSpecialCharacterDataModule extends ResourceLoaderModule {
+ private $path = "resources/src/mediawiki.language/specialcharacters.json";
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /**
+ * Get all the dynamic data.
+ *
+ * @return array
+ */
+ protected function getData() {
+ return json_decode( file_get_contents( $this->path ) );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall(
+ 'mw.language.setSpecialCharacters',
+ array(
+ $this->getData()
+ ),
+ ResourceLoader::inDebugMode()
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return static::safeFilemtime( $this->path );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5( serialize( $this->getData() ) );
+ }
+
+ /**
+ * @return array
+ */
+ public function getDependencies() {
+ return array( 'mediawiki.language' );
+ }
+
+ /**
+ * @return array
+ */
+ public function getMessages() {
+ return array(
+ 'special-characters-group-latin',
+ 'special-characters-group-latinextended',
+ 'special-characters-group-ipa',
+ 'special-characters-group-symbols',
+ 'special-characters-group-greek',
+ 'special-characters-group-cyrillic',
+ 'special-characters-group-arabic',
+ 'special-characters-group-arabicextended',
+ 'special-characters-group-persian',
+ 'special-characters-group-hebrew',
+ 'special-characters-group-bangla',
+ 'special-characters-group-tamil',
+ 'special-characters-group-telugu',
+ 'special-characters-group-sinhala',
+ 'special-characters-group-devanagari',
+ 'special-characters-group-gujarati',
+ 'special-characters-group-thai',
+ 'special-characters-group-lao',
+ 'special-characters-group-khmer',
+ 'special-characters-title-endash',
+ 'special-characters-title-emdash',
+ 'special-characters-title-minus'
+ );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 78fe8e01..b2fbae9c 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -82,6 +82,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgServerName' => $conf->get( 'ServerName' ),
'wgUserLanguage' => $context->getLanguage(),
'wgContentLanguage' => $wgContLang->getCode(),
+ 'wgTranslateNumerals' => $conf->get( 'TranslateNumerals' ),
'wgVersion' => $conf->get( 'Version' ),
'wgEnableAPI' => $conf->get( 'EnableAPI' ),
'wgEnableWriteAPI' => $conf->get( 'EnableWriteAPI' ),
@@ -90,11 +91,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgNamespaceIds' => $namespaceIds,
'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' => SpecialUpload::rotationEnabled(),
'wgAvailableSkins' => Skin::getSkinNames(),
'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
// MediaWiki sets cookies to have this prefix by default
@@ -109,7 +106,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
);
- wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
+ Hooks::run( 'ResourceLoaderGetConfigVars', array( &$vars ) );
$this->configVars[$hash] = $vars;
return $this->configVars[$hash];
@@ -150,7 +147,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * Optimize the dependency tree in $this->modules and return it.
+ * Optimize the dependency tree in $this->modules.
*
* The optimization basically works like this:
* Given we have module A with the dependencies B and C
@@ -158,11 +155,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
* 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
+ * This way we can reasonably reduce the amount of module registration
* data send to the client.
*
* @param array &$registryData Modules keyed by name with properties:
- * - string 'version'
+ * - number 'version'
* - array 'dependencies'
* - string|null 'group'
* - string 'source'
@@ -191,7 +188,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
* @return string JavaScript code for registering all modules with the client loader
*/
public function getModuleRegistrations( ResourceLoaderContext $context ) {
- wfProfileIn( __METHOD__ );
$resourceLoader = $context->getResourceLoader();
$target = $context->getRequest()->getVal( 'target', 'desktop' );
@@ -214,12 +210,10 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
continue;
}
- // 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
+ // Coerce module timestamp to UNIX timestamp.
+ // getModifiedTime() is supposed to return a UNIX timestamp, but custom implementations
+ // might forget. TODO: Maybe emit warning?
$moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
- $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() ) {
@@ -232,8 +226,14 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
);
}
+ $mtime = max(
+ $moduleMtime,
+ wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) )
+ );
+
$registryData[$name] = array(
- 'version' => $mtime,
+ // Convert to numbers as wfTimestamp always returns a string, even for TS_UNIX
+ 'version' => (int) $mtime,
'dependencies' => $module->getDependencies(),
'group' => $module->getGroup(),
'source' => $module->getSource(),
@@ -254,7 +254,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
if ( $data['loader'] !== false ) {
$out .= ResourceLoader::makeCustomLoaderScript(
$name,
- wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ),
+ $data['version'],
$data['dependencies'],
$data['group'],
$data['source'],
@@ -263,63 +263,21 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
continue;
}
- 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']
- );
- }
+ // Call mw.loader.register(name, timestamp, dependencies, group, source, skip)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group'],
+ // Swap default (local) for null
+ $data['source'] === 'local' ? null : $data['source'],
+ $data['skip']
+ );
}
// Register modules
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
- wfProfileOut( __METHOD__ );
return $out;
}
@@ -333,7 +291,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * Base modules required for the the base environment of ResourceLoader
+ * Base modules required for the base environment of ResourceLoader
*
* @return array
*/
@@ -355,7 +313,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// Get the latest version
$loader = $context->getResourceLoader();
- $version = 0;
+ $version = 1;
foreach ( $moduleNames as $moduleName ) {
$version = max( $version,
$loader->getModule( $moduleName )->getModifiedTime( $context )
@@ -390,18 +348,28 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$registrations = $this->getModuleRegistrations( $context );
// Fix indentation
$registrations = str_replace( "\n", "\n\t", trim( $registrations ) );
+ $mwMapJsCall = Xml::encodeJsCall(
+ 'mw.Map',
+ array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) )
+ );
+ $mwConfigSetJsCall = Xml::encodeJsCall(
+ 'mw.config.set',
+ array( $configuration ),
+ ResourceLoader::inDebugMode()
+ );
+
$out .= "var startUp = function () {\n" .
"\tmw.config = new " .
- Xml::encodeJsCall( 'mw.Map', array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" .
+ $mwMapJsCall . "\n" .
"\t$registrations\n" .
- "\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
+ "\t" . $mwConfigSetJsCall .
"};\n";
// Conditional script injection
$scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) );
$out .= "if ( isCompatible() ) {\n" .
"\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
- "}";
+ "\n}";
}
return $out;
@@ -440,8 +408,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// ATTENTION!: Because of the line below, this is not going to cause
// infinite recursion - think carefully before making changes to this
// code!
- // Pre-populate modifiedTime with something because the the loop over
- // all modules below includes the the startup module (this module).
+ // Pre-populate modifiedTime with something because the loop over
+ // all modules below includes the startup module (this module).
$this->modifiedTime[$hash] = 1;
foreach ( $loader->getModuleNames() as $name ) {
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
index 40274c63..472ceb26 100644
--- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -42,8 +42,7 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
if ( !isset( $this->modifiedTime[$hash] ) ) {
- global $wgUser;
- $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $context->getUserObj()->getTouched() );
}
return $this->modifiedTime[$hash];
@@ -54,13 +53,11 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgUser;
-
if ( !$this->getConfig()->get( 'AllowUserCssPrefs' ) ) {
return array();
}
- $options = $wgUser->getOptions();
+ $options = $context->getUserObj()->getOptions();
// Build CSS rules
$rules = array();
@@ -93,11 +90,4 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
public function getGroup() {
return 'private';
}
-
- /**
- * @return array
- */
- public function getDependencies() {
- return array( 'mediawiki.user' );
- }
}
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderUserDefaultsModule.php
index 61927d77..5f4bc16b 100644
--- a/includes/resourceloader/ResourceLoaderNoscriptModule.php
+++ b/includes/resourceloader/ResourceLoaderUserDefaultsModule.php
@@ -1,6 +1,6 @@
<?php
/**
- * Resource loader for site customizations for users without JavaScript enabled.
+ * Resource loader module for default user preferences.
*
* 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
@@ -18,37 +18,45 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @author Trevor Parscal
- * @author Roan Kattouw
+ * @author Ori Livneh
*/
/**
- * Module for site customizations
+ * Module for default user preferences.
*/
-class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
+class ResourceLoaderUserDefaultsModule extends ResourceLoaderModule {
- /* Protected Methods */
+ /* Protected Members */
+
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /* Methods */
/**
- * Gets list of pages used by this module. Obviously, it makes absolutely no
- * sense to include JavaScript files here... :D
- *
* @param ResourceLoaderContext $context
- *
- * @return array List of pages
+ * @return string Hash
*/
- protected function getPages( ResourceLoaderContext $context ) {
- return array( 'MediaWiki:Noscript.css' => array( 'type' => 'style' ) );
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5( serialize( User::getDefaultOptions() ) );
}
- /* Methods */
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return $this->getHashMtime( $context );
+ }
/**
- * Gets group name
- *
- * @return string Name of group
+ * @param ResourceLoaderContext $context
+ * @return string
*/
- public function getGroup() {
- return 'noscript';
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall(
+ 'mw.user.options.set',
+ array( User::getDefaultOptions() ),
+ ResourceLoader::inDebugMode()
+ );
}
}
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
index 7cf19420..417cfced 100644
--- a/includes/resourceloader/ResourceLoaderUserGroupsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -25,39 +25,23 @@
*/
class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
- /* Protected Members */
-
protected $origin = self::ORIGIN_USER_SITEWIDE;
protected $targets = array( 'desktop', 'mobile' );
- /* Protected Methods */
-
/**
* @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUser;
-
- $userName = $context->getUser();
- if ( $userName === null ) {
- return array();
- }
-
$useSiteJs = $this->getConfig()->get( 'UseSiteJs' );
$useSiteCss = $this->getConfig()->get( 'UseSiteCss' );
if ( !$useSiteJs && !$useSiteCss ) {
return array();
}
- // Use $wgUser is possible; allows to skip a lot of code
- if ( is_object( $wgUser ) && $wgUser->getName() == $userName ) {
- $user = $wgUser;
- } else {
- $user = User::newFromName( $userName );
- if ( !$user instanceof User ) {
- return array();
- }
+ $user = $context->getUserObj();
+ if ( !$user || $user->isAnon() ) {
+ return array();
}
$pages = array();
@@ -75,9 +59,9 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
return $pages;
}
- /* Methods */
-
/**
+ * Get group name
+ *
* @return string
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index 1b6d1de0..a0978445 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -27,47 +27,37 @@
*/
class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
- /* Protected Members */
-
protected $origin = self::ORIGIN_USER_INDIVIDUAL;
- /* Protected Methods */
-
/**
+ * Get list of pages used by this module
+ *
* @param ResourceLoaderContext $context
- * @return array
+ * @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
- $username = $context->getUser();
-
- if ( $username === null ) {
- return array();
- }
-
$allowUserJs = $this->getConfig()->get( 'AllowUserJs' );
$allowUserCss = $this->getConfig()->get( 'AllowUserCss' );
-
if ( !$allowUserJs && !$allowUserCss ) {
return array();
}
- // Get the normalized title of the user's user page
- $userpageTitle = Title::makeTitleSafe( NS_USER, $username );
-
- if ( !$userpageTitle instanceof Title ) {
+ $user = $context->getUserObj();
+ if ( !$user || $user->isAnon() ) {
return array();
}
- $userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works
+ // Needed so $excludepages works
+ $userPage = $user->getUserPage()->getPrefixedDBkey();
$pages = array();
if ( $allowUserJs ) {
- $pages["$userpage/common.js"] = array( 'type' => 'script' );
- $pages["$userpage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
+ $pages["$userPage/common.js"] = array( 'type' => 'script' );
+ $pages["$userPage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
}
if ( $allowUserCss ) {
- $pages["$userpage/common.css"] = array( 'type' => 'style' );
- $pages["$userpage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
+ $pages["$userPage/common.css"] = array( 'type' => 'style' );
+ $pages["$userPage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
}
// Hack for bug 26283: if we're on a preview page for a CSS/JS page,
@@ -82,9 +72,9 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
return $pages;
}
- /* Methods */
-
/**
+ * Get group name
+ *
* @return string
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index bd97a8e5..84c1906d 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -38,14 +38,20 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
/* Methods */
/**
+ * @return array List of module names as strings
+ */
+ public function getDependencies() {
+ return array( 'user.defaults' );
+ }
+
+ /**
* @param ResourceLoaderContext $context
- * @return array|int|mixed
+ * @return int
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
if ( !isset( $this->modifiedTime[$hash] ) ) {
- global $wgUser;
- $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $context->getUserObj()->getTouched() );
}
return $this->modifiedTime[$hash];
@@ -56,9 +62,8 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
- global $wgUser;
return Xml::encodeJsCall( 'mw.user.options.set',
- array( $wgUser->getOptions() ),
+ array( $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS ) ),
ResourceLoader::inDebugMode()
);
}
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 668467ca..ccd1dfd0 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -37,15 +37,16 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
/**
* Fetch the tokens for the current user.
*
+ * @param ResourceLoaderContext $context
* @return array List of tokens keyed by token type
*/
- protected function contextUserTokens() {
- global $wgUser;
+ protected function contextUserTokens( ResourceLoaderContext $context ) {
+ $user = $context->getUserObj();
return array(
- 'editToken' => $wgUser->getEditToken(),
- 'patrolToken' => $wgUser->getEditToken( 'patrol' ),
- 'watchToken' => $wgUser->getEditToken( 'watch' ),
+ 'editToken' => $user->getEditToken(),
+ 'patrolToken' => $user->getEditToken( 'patrol' ),
+ 'watchToken' => $user->getEditToken( 'watch' ),
);
}
@@ -55,7 +56,7 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
*/
public function getScript( ResourceLoaderContext $context ) {
return Xml::encodeJsCall( 'mw.user.tokens.set',
- array( $this->contextUserTokens() ),
+ array( $this->contextUserTokens( $context ) ),
ResourceLoader::inDebugMode()
);
}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index de61fc55..7b44cc67 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -29,17 +29,37 @@
* because of its dependence on the functionality of
* Title::isCssJsSubpage.
*/
-abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
+class ResourceLoaderWikiModule extends ResourceLoaderModule {
- /* Protected Members */
-
- # Origin is user-supplied code
+ // Origin defaults to users with sitewide authority
protected $origin = self::ORIGIN_USER_SITEWIDE;
// In-object cache for title info
protected $titleInfo = array();
- /* Abstract Protected Methods */
+ // List of page names that contain CSS
+ protected $styles = array();
+
+ // List of page names that contain JavaScript
+ protected $scripts = array();
+
+ // Group of module
+ protected $group;
+
+ /**
+ * @param array $options For back-compat, this can be omitted in favour of overwriting getPages.
+ */
+ public function __construct( array $options = null ) {
+ if ( isset( $options['styles'] ) ) {
+ $this->styles = $options['styles'];
+ }
+ if ( isset( $options['scripts'] ) ) {
+ $this->scripts = $options['scripts'];
+ }
+ if ( isset( $options['group'] ) ) {
+ $this->group = $options['group'];
+ }
+ }
/**
* Subclasses should return an associative array of resources in the module.
@@ -57,9 +77,34 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @param ResourceLoaderContext $context
* @return array
*/
- abstract protected function getPages( ResourceLoaderContext $context );
+ protected function getPages( ResourceLoaderContext $context ) {
+ $config = $this->getConfig();
+ $pages = array();
- /* Protected Methods */
+ // Filter out pages from origins not allowed by the current wiki configuration.
+ if ( $config->get( 'UseSiteJs' ) ) {
+ foreach ( $this->scripts as $script ) {
+ $pages[$script] = array( 'type' => 'script' );
+ }
+ }
+
+ if ( $config->get( 'UseSiteCss' ) ) {
+ foreach ( $this->styles as $style ) {
+ $pages[$style] = array( 'type' => 'style' );
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Get group name
+ *
+ * @return string
+ */
+ public function getGroup() {
+ return $this->group;
+ }
/**
* Get the Database object used in getTitleMTimes(). Defaults to the local slave DB
@@ -70,7 +115,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* In particular, it doesn't work for getting the content of JS and CSS pages. That functionality
* will use the local DB irrespective of the return value of this method.
*
- * @return DatabaseBase|null
+ * @return IDatabase|null
*/
protected function getDB() {
return wfGetDB( DB_SLAVE );
@@ -81,9 +126,15 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @return null|string
*/
protected function getContent( $title ) {
- if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
+ $handler = ContentHandler::getForTitle( $title );
+ if ( $handler->isSupportedFormat( CONTENT_FORMAT_CSS ) ) {
+ $format = CONTENT_FORMAT_CSS;
+ } elseif ( $handler->isSupportedFormat( CONTENT_FORMAT_JAVASCRIPT ) ) {
+ $format = CONTENT_FORMAT_JAVASCRIPT;
+ } else {
return null;
}
+
$revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
if ( !$revision ) {
return null;
@@ -96,18 +147,9 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return null;
}
- 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->serialize( $format );
}
- /* Methods */
-
/**
* @param ResourceLoaderContext $context
* @return string
@@ -165,13 +207,13 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/**
* @param ResourceLoaderContext $context
- * @return int|mixed
+ * @return int
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
+ $modifiedTime = 1;
$titleInfo = $this->getTitleInfo( $context );
if ( count( $titleInfo ) ) {
- $mtimes = array_map( function( $value ) {
+ $mtimes = array_map( function ( $value ) {
return $value['timestamp'];
}, $titleInfo );
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
@@ -227,8 +269,8 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* Get the modification times of all titles that would be loaded for
* a given context.
* @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
+ * @return array Keyed by page dbkey. Value is an array with 'length' and 'timestamp'
+ * keys, where the timestamp is a UNIX timestamp
*/
protected function getTitleInfo( ResourceLoaderContext $context ) {
$dbr = $this->getDB();