From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- .../resourceloader/ResourceLoaderFileModule.php | 473 ++++++++++++++------- 1 file changed, 317 insertions(+), 156 deletions(-) (limited to 'includes/resourceloader/ResourceLoaderFileModule.php') diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php index 9ed181ed..dc8b14a2 100644 --- a/includes/resourceloader/ResourceLoaderFileModule.php +++ b/includes/resourceloader/ResourceLoaderFileModule.php @@ -26,111 +26,131 @@ * ResourceLoader module based on local JavaScript/CSS files. */ class ResourceLoaderFileModule extends ResourceLoaderModule { - /* Protected Members */ - /** String: Local base path, see __construct() */ + /** @var string Local base path, see __construct() */ protected $localBasePath = ''; - /** String: Remote base path, see __construct() */ + + /** @var string Remote base path, see __construct() */ protected $remoteBasePath = ''; + /** - * Array: List of paths to JavaScript files to always include + * @var array List of paths to JavaScript files to always include * @par Usage: * @code * array( [file-path], [file-path], ... ) * @endcode */ protected $scripts = array(); + /** - * Array: List of JavaScript files to include when using a specific language + * @var array List of JavaScript files to include when using a specific language * @par Usage: * @code * array( [language-code] => array( [file-path], [file-path], ... ), ... ) * @endcode */ protected $languageScripts = array(); + /** - * Array: List of JavaScript files to include when using a specific skin + * @var array List of JavaScript files to include when using a specific skin * @par Usage: * @code * array( [skin-name] => array( [file-path], [file-path], ... ), ... ) * @endcode */ protected $skinScripts = array(); + /** - * Array: List of paths to JavaScript files to include in debug mode + * @var array List of paths to JavaScript files to include in debug mode * @par Usage: * @code * array( [skin-name] => array( [file-path], [file-path], ... ), ... ) * @endcode */ protected $debugScripts = array(); + /** - * Array: List of paths to JavaScript files to include in the startup module + * @var array List of paths to JavaScript files to include in the startup module * @par Usage: * @code * array( [file-path], [file-path], ... ) * @endcode */ protected $loaderScripts = array(); + /** - * Array: List of paths to CSS files to always include + * @var array List of paths to CSS files to always include * @par Usage: * @code * array( [file-path], [file-path], ... ) * @endcode */ protected $styles = array(); + /** - * Array: List of paths to CSS files to include when using specific skins + * @var array List of paths to CSS files to include when using specific skins * @par Usage: * @code * array( [file-path], [file-path], ... ) * @endcode */ protected $skinStyles = array(); + /** - * Array: List of modules this module depends on + * @var array List of modules this module depends on * @par Usage: * @code * array( [file-path], [file-path], ... ) * @endcode */ protected $dependencies = array(); + + /** + * @var string File name containing the body of the skip function + */ + protected $skipFunction = null; + /** - * Array: List of message keys used by this module + * @var array List of message keys used by this module * @par Usage: * @code * array( [message-key], [message-key], ... ) * @endcode */ protected $messages = array(); - /** String: Name of group to load this module in */ + + /** @var string Name of group to load this module in */ protected $group; - /** String: Position on the page to load this module at */ + + /** @var string Position on the page to load this module at */ protected $position = 'bottom'; - /** Boolean: Link to raw files in debug mode */ + + /** @var bool Link to raw files in debug mode */ protected $debugRaw = true; - /** Boolean: Whether mw.loader.state() call should be omitted */ + + /** @var bool Whether mw.loader.state() call should be omitted */ protected $raw = false; + protected $targets = array( 'desktop' ); /** - * Boolean: Whether getStyleURLsForDebug should return raw file paths, + * @var bool Whether getStyleURLsForDebug should return raw file paths, * or return load.php urls */ protected $hasGeneratedStyles = false; /** - * Array: Cache for mtime + * @var array Cache for mtime * @par Usage: * @code * array( [hash] => [mtime], [hash] => [mtime], ... ) * @endcode */ protected $modifiedTime = array(); + /** - * Array: Place where readStyleFile() tracks file dependencies + * @var array Place where readStyleFile() tracks file dependencies * @par Usage: * @code * array( [file-path], [file-path], ... ) @@ -148,7 +168,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults * to $IP * @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults - * to $wgScriptPath + * to $wgResourceBasePath * * Below is a description for the $options array: * @throws MWException @@ -157,10 +177,12 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * array( * // Base path to prepend to all local paths in $options. Defaults to $IP * 'localBasePath' => [base path], - * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath + * // Base path to prepend to all remote paths in $options. Defaults to $wgResourceBasePath * 'remoteBasePath' => [base path], * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath * 'remoteExtPath' => [base path], + * // Equivalent of remoteBasePath, but relative to $wgStylePath + * 'remoteSkinPath' => [base path], * // Scripts to always include * 'scripts' => [file path string or array of file path strings], * // Scripts to include in specific language contexts @@ -189,25 +211,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * 'group' => [group name string], * // Position on the page to load this module at * 'position' => ['bottom' (default) or 'top'] + * // Function that, if it returns true, makes the loader skip this module. + * // The file must contain valid JavaScript for execution in a private function. + * // The file must not contain the "function () {" and "}" wrapper though. + * 'skipFunction' => [file path] * ) * @endcode */ - public function __construct( $options = array(), $localBasePath = null, + public function __construct( + $options = array(), + $localBasePath = null, $remoteBasePath = null ) { - global $IP, $wgScriptPath, $wgResourceBasePath; - $this->localBasePath = $localBasePath === null ? $IP : $localBasePath; - if ( $remoteBasePath !== null ) { - $this->remoteBasePath = $remoteBasePath; - } else { - $this->remoteBasePath = $wgResourceBasePath === null ? $wgScriptPath : $wgResourceBasePath; - } - - if ( isset( $options['remoteExtPath'] ) ) { - global $wgExtensionAssetsPath; - $this->remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath']; - } + // localBasePath and remoteBasePath both have unbelievably long fallback chains + // and need to be handled separately. + list( $this->localBasePath, $this->remoteBasePath ) = + self::extractBasePaths( $options, $localBasePath, $remoteBasePath ); + // Extract, validate and normalise remaining options foreach ( $options as $member => $option ) { switch ( $member ) { // Lists of file paths @@ -241,13 +262,16 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { case 'dependencies': case 'messages': case 'targets': - $this->{$member} = (array)$option; + // Normalise + $option = array_values( array_unique( (array)$option ) ); + sort( $option ); + + $this->{$member} = $option; break; // Single strings case 'group': case 'position': - case 'localBasePath': - case 'remoteBasePath': + case 'skipFunction': $this->{$member} = (string)$option; break; // Single booleans @@ -257,16 +281,64 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { break; } } + } + + /** + * Extract a pair of local and remote base paths from module definition information. + * Implementation note: the amount of global state used in this function is staggering. + * + * @param array $options Module definition + * @param string $localBasePath Path to use if not provided in module definition. Defaults + * to $IP + * @param string $remoteBasePath Path to use if not provided in module definition. Defaults + * to $wgResourceBasePath + * @return array Array( localBasePath, remoteBasePath ) + */ + public static function extractBasePaths( + $options = array(), + $localBasePath = null, + $remoteBasePath = null + ) { + global $IP, $wgResourceBasePath; + + // The different ways these checks are done, and their ordering, look very silly, + // but were preserved for backwards-compatibility just in case. Tread lightly. + + $localBasePath = $localBasePath === null ? $IP : $localBasePath; + if ( $remoteBasePath === null ) { + $remoteBasePath = $wgResourceBasePath; + } + + if ( isset( $options['remoteExtPath'] ) ) { + global $wgExtensionAssetsPath; + $remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath']; + } + + if ( isset( $options['remoteSkinPath'] ) ) { + global $wgStylePath; + $remoteBasePath = $wgStylePath . '/' . $options['remoteSkinPath']; + } + + if ( array_key_exists( 'localBasePath', $options ) ) { + $localBasePath = (string)$options['localBasePath']; + } + + if ( array_key_exists( 'remoteBasePath', $options ) ) { + $remoteBasePath = (string)$options['remoteBasePath']; + } + // Make sure the remote base path is a complete valid URL, // but possibly protocol-relative to avoid cache pollution - $this->remoteBasePath = wfExpandUrl( $this->remoteBasePath, PROTO_RELATIVE ); + $remoteBasePath = wfExpandUrl( $remoteBasePath, PROTO_RELATIVE ); + + return array( $localBasePath, $remoteBasePath ); } /** * Gets all scripts for a given context concatenated together. * * @param ResourceLoaderContext $context Context in which to generate script - * @return string: JavaScript code for $context + * @return string JavaScript code for $context */ public function getScript( ResourceLoaderContext $context ) { $files = $this->getScriptFiles( $context ); @@ -293,27 +365,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** - * Gets loader script. + * Get loader script. * - * @return string: JavaScript code to be added to startup module + * @return string|bool JavaScript code to be added to startup module */ public function getLoaderScript() { - if ( count( $this->loaderScripts ) == 0 ) { + if ( count( $this->loaderScripts ) === 0 ) { return false; } return $this->readScriptFiles( $this->loaderScripts ); } /** - * Gets all styles for a given context concatenated together. + * Get all styles for a given context. * - * @param ResourceLoaderContext $context Context in which to generate styles - * @return string: CSS code for $context + * @param ResourceLoaderContext $context + * @return array CSS code for $context as an associative array mapping media type to CSS text. */ public function getStyles( ResourceLoaderContext $context ) { $styles = $this->readStyleFiles( $this->getStyleFiles( $context ), - $this->getFlip( $context ) + $this->getFlip( $context ), + $context ); // Collect referenced files $this->localFileRefs = array_unique( $this->localFileRefs ); @@ -360,7 +433,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets list of message keys used by this module. * - * @return array: List of message keys + * @return array List of message keys */ public function getMessages() { return $this->messages; @@ -369,7 +442,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets the name of the group this module should be loaded in. * - * @return string: Group name + * @return string Group name */ public function getGroup() { return $this->group; @@ -385,12 +458,33 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets list of names of modules this module depends on. * - * @return array: List of module names + * @return array List of module names */ public function getDependencies() { return $this->dependencies; } + /** + * Get the skip function. + * + * @return string|null + */ + public function getSkipFunction() { + if ( !$this->skipFunction ) { + return null; + } + + $localPath = $this->getLocalPath( $this->skipFunction ); + if ( !file_exists( $localPath ) ) { + throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" ); + } + $contents = file_get_contents( $localPath ); + if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) { + $contents = $this->validateScriptFile( $localPath, $contents ); + } + return $contents; + } + /** * @return bool */ @@ -409,7 +503,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @param ResourceLoaderContext $context Context in which to calculate * the modified time - * @return int: UNIX timestamp + * @return int UNIX timestamp * @see ResourceLoaderModule::getFileDependencies */ public function getModifiedTime( ResourceLoaderContext $context ) { @@ -425,10 +519,11 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { foreach ( $styles as $styleFiles ) { $files = array_merge( $files, $styleFiles ); } - $skinFiles = self::tryForKey( - self::collateFilePathListByOption( $this->skinStyles, 'media', 'all' ), - $context->getSkin(), - 'default' + + $skinFiles = self::collateFilePathListByOption( + self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), + 'media', + 'all' ); foreach ( $skinFiles as $styleFiles ) { $files = array_merge( $files, $styleFiles ); @@ -443,6 +538,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ), $this->loaderScripts ); + if ( $this->skipFunction ) { + $files[] = $this->skipFunction; + } $files = array_map( array( $this, 'getLocalPath' ), $files ); // File deps need to be treated separately because they're already prefixed $files = array_merge( $files, $this->getFileDependencies( $context->getSkin() ) ); @@ -450,36 +548,82 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { // If a module is nothing but a list of dependencies, we need to avoid // giving max() an empty array if ( count( $files ) === 0 ) { + $this->modifiedTime[$context->getHash()] = 1; wfProfileOut( __METHOD__ ); - return $this->modifiedTime[$context->getHash()] = 1; + return $this->modifiedTime[$context->getHash()]; } wfProfileIn( __METHOD__ . '-filemtime' ); $filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) ); wfProfileOut( __METHOD__ . '-filemtime' ); + $this->modifiedTime[$context->getHash()] = max( $filesMtime, - $this->getMsgBlobMtime( $context->getLanguage() ) ); + $this->getMsgBlobMtime( $context->getLanguage() ), + $this->getDefinitionMtime( $context ) + ); wfProfileOut( __METHOD__ ); return $this->modifiedTime[$context->getHash()]; } + /** + * Get the definition summary for this module. + * + * @param ResourceLoaderContext $context + * @return array + */ + public function getDefinitionSummary( ResourceLoaderContext $context ) { + $summary = array( + 'class' => get_class( $this ), + ); + foreach ( array( + 'scripts', + 'debugScripts', + 'loaderScripts', + 'styles', + 'languageScripts', + 'skinScripts', + 'skinStyles', + 'dependencies', + 'messages', + 'targets', + 'group', + 'position', + 'skipFunction', + 'localBasePath', + 'remoteBasePath', + 'debugRaw', + 'raw', + ) as $member ) { + $summary[$member] = $this->{$member}; + }; + return $summary; + } + /* Protected Methods */ /** - * @param string $path + * @param string|ResourceLoaderFilePath $path * @return string */ protected function getLocalPath( $path ) { + if ( $path instanceof ResourceLoaderFilePath ) { + return $path->getLocalPath(); + } + return "{$this->localBasePath}/$path"; } /** - * @param string $path + * @param string|ResourceLoaderFilePath $path * @return string */ protected function getRemotePath( $path ) { + if ( $path instanceof ResourceLoaderFilePath ) { + return $path->getRemotePath(); + } + return "{$this->remoteBasePath}/$path"; } @@ -488,7 +632,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @since 1.22 * @param string $path - * @return string: the stylesheet language name + * @return string The stylesheet language name */ public function getStyleSheetLang( $path ) { return preg_match( '/\.less$/i', $path ) ? 'less' : 'css'; @@ -499,9 +643,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @param array $list List of file paths in any combination of index/path * or path/options pairs - * @param string $option option name - * @param mixed $default default value if the option isn't set - * @return array: List of file paths, collated by $option + * @param string $option Option name + * @param mixed $default Default value if the option isn't set + * @return array List of file paths, collated by $option */ protected static function collateFilePathListByOption( array $list, $option, $default ) { $collatedFiles = array(); @@ -525,31 +669,31 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** - * Gets a list of element that match a key, optionally using a fallback key. + * Get a list of element that match a key, optionally using a fallback key. * * @param array $list List of lists to select from * @param string $key Key to look for in $map * @param string $fallback Key to look for in $list if $key doesn't exist - * @return array: List of elements from $map which matched $key or $fallback, - * or an empty list in case of no match + * @return array List of elements from $map which matched $key or $fallback, + * or an empty list in case of no match */ protected static function tryForKey( array $list, $key, $fallback = null ) { if ( isset( $list[$key] ) && is_array( $list[$key] ) ) { return $list[$key]; } elseif ( is_string( $fallback ) && isset( $list[$fallback] ) - && is_array( $list[$fallback] ) ) - { + && is_array( $list[$fallback] ) + ) { return $list[$fallback]; } return array(); } /** - * Gets a list of file paths for all scripts in this module, in order of propper execution. + * Get a list of file paths for all scripts in this module, in order of proper execution. * * @param ResourceLoaderContext $context - * @return array: List of file paths + * @return array List of file paths */ protected function getScriptFiles( ResourceLoaderContext $context ) { $files = array_merge( @@ -561,39 +705,82 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { $files = array_merge( $files, $this->debugScripts ); } - return array_unique( $files ); + return array_unique( $files, SORT_REGULAR ); } /** - * Gets a list of file paths for all styles in this module, in order of propper inclusion. + * Get a list of file paths for all styles in this module, in order of proper inclusion. * * @param ResourceLoaderContext $context - * @return array: List of file paths + * @return array List of file paths */ - protected function getStyleFiles( ResourceLoaderContext $context ) { + public function getStyleFiles( ResourceLoaderContext $context ) { return array_merge_recursive( self::collateFilePathListByOption( $this->styles, 'media', 'all' ), self::collateFilePathListByOption( - self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 'media', 'all' + self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), + 'media', + 'all' ) ); } /** - * Returns all style files used by this module + * Gets a list of file paths for all skin styles in the module used by + * the skin. + * + * @param string $skinName The name of the skin + * @return array A list of file paths collated by media type + */ + protected function getSkinStyleFiles( $skinName ) { + return self::collateFilePathListByOption( + self::tryForKey( $this->skinStyles, $skinName ), + 'media', + 'all' + ); + } + + /** + * Gets a list of file paths for all skin style files in the module, + * for all available skins. + * + * @return array A list of file paths collated by media type + */ + protected function getAllSkinStyleFiles() { + $styleFiles = array(); + $internalSkinNames = array_keys( Skin::getSkinNames() ); + $internalSkinNames[] = 'default'; + + foreach ( $internalSkinNames as $internalSkinName ) { + $styleFiles = array_merge_recursive( + $styleFiles, + $this->getSkinStyleFiles( $internalSkinName ) + ); + } + + return $styleFiles; + } + + /** + * Returns all style files and all skin style files used by this module. + * * @return array */ public function getAllStyleFiles() { - $files = array(); - foreach( (array)$this->styles as $key => $value ) { - if ( is_array( $value ) ) { - $path = $key; - } else { - $path = $value; + $collatedStyleFiles = array_merge_recursive( + self::collateFilePathListByOption( $this->styles, 'media', 'all' ), + $this->getAllSkinStyleFiles() + ); + + $result = array(); + + foreach ( $collatedStyleFiles as $media => $styleFiles ) { + foreach ( $styleFiles as $styleFile ) { + $result[] = $this->getLocalPath( $styleFile ); } - $files[] = $this->getLocalPath( $path ); } - return $files; + + return $result; } /** @@ -601,21 +788,20 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @param array $scripts List of file paths to scripts to read, remap and concetenate * @throws MWException - * @return string: Concatenated and remapped JavaScript data from $scripts + * @return string Concatenated and remapped JavaScript data from $scripts */ protected function readScriptFiles( array $scripts ) { - global $wgResourceLoaderValidateStaticJS; if ( empty( $scripts ) ) { return ''; } $js = ''; - foreach ( array_unique( $scripts ) as $fileName ) { + foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) { $localPath = $this->getLocalPath( $fileName ); if ( !file_exists( $localPath ) ) { throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" ); } $contents = file_get_contents( $localPath ); - if ( $wgResourceLoaderValidateStaticJS ) { + if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) { // Static files don't really need to be checked as often; unlike // on-wiki module they shouldn't change unexpectedly without // admin interference. @@ -631,26 +817,24 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @param array $styles List of media type/list of file paths pairs, to read, remap and * concetenate - * * @param bool $flip + * @param ResourceLoaderContext $context (optional) * - * @return array: List of concatenated and remapped CSS data from $styles, + * @throws MWException + * @return array List of concatenated and remapped CSS data from $styles, * keyed by media type */ - protected function readStyleFiles( array $styles, $flip ) { + public function readStyleFiles( array $styles, $flip, $context = null ) { if ( empty( $styles ) ) { return array(); } foreach ( $styles as $media => $files ) { - $uniqueFiles = array_unique( $files ); - $styles[$media] = implode( - "\n", - array_map( - array( $this, 'readStyleFile' ), - $uniqueFiles, - array_fill( 0, count( $uniqueFiles ), $flip ) - ) - ); + $uniqueFiles = array_unique( $files, SORT_REGULAR ); + $styleFiles = array(); + foreach ( $uniqueFiles as $file ) { + $styleFiles[] = $this->readStyleFile( $file, $flip, $context ); + } + $styles[$media] = implode( "\n", $styleFiles ); } return $styles; } @@ -662,20 +846,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * @param string $path File path of style file to read * @param bool $flip + * @param ResourceLoaderContext $context (optional) * - * @return string: CSS data in script file - * @throws MWException if the file doesn't exist + * @return string CSS data in script file + * @throws MWException If the file doesn't exist */ - protected function readStyleFile( $path, $flip ) { + protected function readStyleFile( $path, $flip, $context = null ) { $localPath = $this->getLocalPath( $path ); + $remotePath = $this->getRemotePath( $path ); if ( !file_exists( $localPath ) ) { $msg = __METHOD__ . ": style file not found: \"$localPath\""; wfDebugLog( 'resourceloader', $msg ); throw new MWException( $msg ); } - if ( $this->getStyleSheetLang( $path ) === 'less' ) { - $style = $this->compileLESSFile( $localPath ); + if ( $this->getStyleSheetLang( $localPath ) === 'less' ) { + $compiler = $this->getLessCompiler( $context ); + $style = $this->compileLessFile( $localPath, $compiler ); $this->hasGeneratedStyles = true; } else { $style = file_get_contents( $localPath ); @@ -684,20 +871,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { if ( $flip ) { $style = CSSJanus::transform( $style, true, false ); } - $dirname = dirname( $path ); - if ( $dirname == '.' ) { - // If $path doesn't have a directory component, don't prepend a dot - $dirname = ''; - } - $dir = $this->getLocalPath( $dirname ); - $remoteDir = $this->getRemotePath( $dirname ); + $localDir = dirname( $localPath ); + $remoteDir = dirname( $remotePath ); // Get and register local file references $this->localFileRefs = array_merge( $this->localFileRefs, - CSSMin::getLocalFileReferences( $style, $dir ) + CSSMin::getLocalFileReferences( $style, $localDir ) ); return CSSMin::remap( - $style, $dir, $remoteDir, true + $style, $localDir, $remoteDir, true ); } @@ -713,64 +895,43 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'] * - * @return array of strings + * @return array Array of strings */ public function getTargets() { return $this->targets; } /** - * Generate a cache key for a LESS file. + * Compile a LESS file into CSS. * - * The cache key varies on the file name and the names and values of global - * LESS variables. + * Keeps track of all used files and adds them to localFileRefs. * * @since 1.22 - * @param string $fileName File name of root LESS file. - * @return string: Cache key + * @throws Exception If lessc encounters a parse error + * @param string $fileName File path of LESS source + * @param lessc $compiler Compiler to use, if not default + * @return string CSS source */ - protected static function getLESSCacheKey( $fileName ) { - $vars = json_encode( ResourceLoader::getLESSVars() ); - $hash = md5( $fileName . $vars ); - return wfMemcKey( 'resourceloader', 'less', $hash ); + protected function compileLessFile( $fileName, $compiler = null ) { + if ( !$compiler ) { + $compiler = $this->getLessCompiler(); + } + $result = $compiler->compileFile( $fileName ); + $this->localFileRefs += array_keys( $compiler->allParsedFiles() ); + return $result; } /** - * Compile a LESS file into CSS. + * Get a LESS compiler instance for this module in given context. * - * If invalid, returns replacement CSS source consisting of the compilation - * error message encoded as a comment. To save work, we cache a result object - * which comprises the compiled CSS and the names & mtimes of the files - * that were processed. lessphp compares the cached & current mtimes and - * recompiles as necessary. + * Just calls ResourceLoader::getLessCompiler() by default to get a global compiler. * - * @since 1.22 - * @param string $fileName File path of LESS source - * @return string: CSS source + * @param ResourceLoaderContext $context + * @throws MWException + * @since 1.24 + * @return lessc */ - protected function compileLESSFile( $fileName ) { - $key = self::getLESSCacheKey( $fileName ); - $cache = wfGetCache( CACHE_ANYTHING ); - - // The input to lessc. Either an associative array representing the - // cached results of a previous compilation, or the string file name if - // no cache result exists. - $source = $cache->get( $key ); - if ( !is_array( $source ) || !isset( $source['root'] ) ) { - $source = $fileName; - } - - $compiler = ResourceLoader::getLessCompiler(); - $result = null; - - $result = $compiler->cachedCompile( $source ); - - if ( !is_array( $result ) ) { - throw new MWException( 'LESS compiler result has type ' . gettype( $result ) . '; array expected.' ); - } - - $this->localFileRefs += array_keys( $result['files'] ); - $cache->set( $key, $result ); - return $result['compiled']; + protected function getLessCompiler( ResourceLoaderContext $context = null ) { + return ResourceLoader::getLessCompiler( $this->getConfig() ); } } -- cgit v1.2.3-54-g00ecf