242 $localBasePath =
null,
243 $remoteBasePath =
null
246 $hasTemplates =
false;
249 list( $this->localBasePath, $this->remoteBasePath ) =
253 foreach ( $options as $member => $option ) {
260 $this->{$member} = is_array( $option ) ? $option : [ $option ];
263 $hasTemplates =
true;
264 $this->{$member} = is_array( $option ) ? $option : [ $option ];
267 case 'languageScripts':
270 if ( !is_array( $option ) ) {
271 throw new InvalidArgumentException(
272 "Invalid collated file path list error. " .
273 "'$option' given, array expected."
276 foreach ( $option as $key => $value ) {
277 if ( !is_string( $key ) ) {
278 throw new InvalidArgumentException(
279 "Invalid collated file path list key error. " .
280 "'$key' given, string expected."
283 $this->{$member}[$key] = is_array( $value ) ? $value : [ $value ];
287 $this->deprecated = $option;
294 $option = array_values( array_unique( (array)$option ) );
297 $this->{$member} = $option;
302 $this->{$member} = (string)$option;
307 $this->{$member} = (bool)$option;
311 if ( isset( $options[
'scripts'] ) && isset( $options[
'packageFiles'] ) ) {
312 throw new InvalidArgumentException(
"A module may not set both 'scripts' and 'packageFiles'" );
314 if ( $hasTemplates ) {
315 $this->dependencies[] =
'mediawiki.template';
317 foreach ( $this->templates as $alias => $templatePath ) {
318 if ( is_int( $alias ) ) {
319 $alias = $this->
getPath( $templatePath );
321 $suffix = explode(
'.', $alias );
322 $suffix = end( $suffix );
323 $compilerModule =
'mediawiki.template.' . $suffix;
324 if ( $suffix !==
'html' && !in_array( $compilerModule, $this->dependencies ) ) {
325 $this->dependencies[] = $compilerModule;
344 $localBasePath =
null,
345 $remoteBasePath =
null
359 if ( isset( $options[
'remoteExtPath'] ) ) {
364 if ( isset( $options[
'remoteSkinPath'] ) ) {
369 if ( array_key_exists(
'localBasePath', $options ) ) {
373 if ( array_key_exists(
'remoteBasePath', $options ) ) {
388 if ( $this->packageFiles !==
null ) {
390 if ( $deprecationScript ) {
392 $mainFile[
'content'] = $deprecationScript . $mainFile[
'content'];
408 $urls[] = OutputPage::transformResourcePath(
422 return $this->debugRaw && !$this->packageFiles;
448 if ( $this->hasGeneratedStyles ) {
451 return parent::getStyleURLsForDebug(
$context );
457 $urls[$mediaType] = [];
458 foreach ( $list as
$file ) {
459 $urls[$mediaType][] = OutputPage::transformResourcePath(
474 return $this->messages;
492 return $this->dependencies;
501 if ( !$this->skipFunction ) {
505 $localPath = $this->
getLocalPath( $this->skipFunction );
506 if ( !file_exists( $localPath ) ) {
507 throw new MWException( __METHOD__ .
": skip function file not found: \"$localPath\"" );
536 $styles = self::collateFilePathListByOption( $this->styles,
'media',
'all' );
537 foreach (
$styles as $styleFiles ) {
538 $files = array_merge( $files, $styleFiles );
541 $skinFiles = self::collateFilePathListByOption(
542 self::tryForKey( $this->skinStyles,
$context->getSkin(),
'default' ),
546 foreach ( $skinFiles as $styleFiles ) {
547 $files = array_merge( $files, $styleFiles );
553 array_filter( array_map(
function ( $fileInfo ) {
554 return $fileInfo[
'filePath'] ??
null;
559 $files = array_merge(
564 $context->getDebug() ? $this->debugScripts : [],
566 self::tryForKey( $this->skinScripts,
$context->getSkin(),
'default' )
568 if ( $this->skipFunction ) {
569 $files[] = $this->skipFunction;
571 $files = array_map( [ $this,
'getLocalPath' ], $files );
577 $files = array_values( array_unique( $files ) );
582 return array_map( [ __CLASS__,
'safeFileHash' ], $files );
592 $summary = parent::getDefinitionSummary(
$context );
614 $options[$member] = $this->{$member};
628 return $fileInfo[
'definitionSummary'] ?? ( $fileInfo[
'content'] ?? null );
633 'options' => $options,
641 $summary[] = [
'lessVars' => $lessVars ];
653 return $path->getPath();
665 return $path->getLocalPath();
668 return "{$this->localBasePath}/$path";
677 return $path->getRemotePath();
680 return "{$this->remoteBasePath}/$path";
691 return preg_match(
'/\.less$/i',
$path ) ?
'less' :
'css';
700 if ( preg_match(
'/\.json$/i',
$path ) ) {
717 foreach ( (array)$list as $key => $value ) {
718 if ( is_int( $key ) ) {
720 if ( !isset( $collatedFiles[$default] ) ) {
721 $collatedFiles[$default] = [];
723 $collatedFiles[$default][] = $value;
724 } elseif ( is_array( $value ) ) {
726 $optionValue = $value[$option] ?? $default;
727 if ( !isset( $collatedFiles[$optionValue] ) ) {
728 $collatedFiles[$optionValue] = [];
730 $collatedFiles[$optionValue][] = $key;
733 return $collatedFiles;
746 if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
764 $files = array_merge(
767 self::tryForKey( $this->skinScripts,
$context->getSkin(),
'default' )
770 $files = array_merge( $files, $this->debugScripts );
773 return array_unique( $files, SORT_REGULAR );
788 $fallbacks = Language::getFallbacksFor(
$lang );
789 foreach ( $fallbacks as
$lang ) {
807 return array_merge_recursive(
808 self::collateFilePathListByOption( $this->styles,
'media',
'all' ),
809 self::collateFilePathListByOption(
810 self::tryForKey( $this->skinStyles,
$context->getSkin(),
'default' ),
825 return self::collateFilePathListByOption(
826 self::tryForKey( $this->skinStyles, $skinName ),
840 $internalSkinNames = array_keys( Skin::getSkinNames() );
841 $internalSkinNames[] =
'default';
843 foreach ( $internalSkinNames as $internalSkinName ) {
844 $styleFiles = array_merge_recursive(
859 $collatedStyleFiles = array_merge_recursive(
860 self::collateFilePathListByOption( $this->styles,
'media',
'all' ),
866 foreach ( $collatedStyleFiles as $media => $styleFiles ) {
867 foreach ( $styleFiles as $styleFile ) {
883 if ( empty( $scripts ) ) {
887 foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) {
889 if ( !file_exists( $localPath ) ) {
890 throw new MWException( __METHOD__ .
": script file not found: \"$localPath\"" );
913 foreach ( $styles as $media => $files ) {
914 $uniqueFiles = array_unique( $files, SORT_REGULAR );
916 foreach ( $uniqueFiles as
$file ) {
919 $styles[$media] = implode(
"\n", $styleFiles );
939 if ( !file_exists( $localPath ) ) {
940 $msg = __METHOD__ .
": style file not found: \"$localPath\"";
947 $this->hasGeneratedStyles =
true;
949 $style = $this->
stripBom( file_get_contents( $localPath ) );
953 $style = CSSJanus::transform( $style,
true,
false );
955 $localDir = dirname( $localPath );
956 $remoteDir = dirname( $remotePath );
958 $localFileRefs = CSSMin::getLocalFileReferences( $style, $localDir );
960 if ( file_exists(
$file ) ) {
961 $this->localFileRefs[] =
$file;
963 $this->missingLocalFileRefs[] =
$file;
968 return CSSMin::remap( $style, $localDir, $remoteDir,
true );
977 return $context->getDirection() ===
'rtl' && !$this->noflip;
986 return $this->targets;
996 $canBeStylesOnly = !(
999 || $this->debugScripts
1001 || $this->languageScripts
1002 || $this->skinScripts
1003 || $this->dependencies
1005 || $this->skipFunction
1006 || $this->packageFiles
1008 return $canBeStylesOnly ? self::LOAD_STYLES : self::LOAD_GENERAL;
1034 $varsHash = hash(
'md4',
serialize( $vars ) );
1035 $cacheKey =
$cache->makeGlobalKey(
'LESS', $fileName, $varsHash );
1036 $cachedCompile =
$cache->get( $cacheKey );
1041 if ( isset( $cachedCompile[
'hash'] ) ) {
1043 if ( $contentHash === $cachedCompile[
'hash'] ) {
1044 $this->localFileRefs = array_merge( $this->localFileRefs, $cachedCompile[
'files'] );
1045 return $cachedCompile[
'css'];
1049 $compiler =
$context->getResourceLoader()->getLessCompiler( $vars );
1050 $css = $compiler->parseFile( $fileName )->getCss();
1051 $files = $compiler->AllParsedFiles();
1052 $this->localFileRefs = array_merge( $this->localFileRefs, $files );
1055 $cache->set( $cacheKey, [
1072 foreach ( $this->templates as $alias => $templatePath ) {
1074 if ( is_int( $alias ) ) {
1075 $alias = $this->
getPath( $templatePath );
1078 if ( file_exists( $localPath ) ) {
1079 $content = file_get_contents( $localPath );
1082 $msg = __METHOD__ .
": template file not found: \"$localPath\"";
1110 if ( isset( $this->expandedPackageFiles[$hash] ) ) {
1111 return $this->expandedPackageFiles[$hash];
1113 if ( $this->packageFiles ===
null ) {
1116 $expandedFiles = [];
1119 foreach ( $this->packageFiles as $alias => $fileInfo ) {
1120 if ( is_string( $fileInfo ) ) {
1121 $fileInfo = [
'name' => $fileInfo,
'file' => $fileInfo ];
1122 } elseif ( !isset( $fileInfo[
'name'] ) ) {
1123 $msg = __METHOD__ .
": invalid package file definition for module " .
1124 "\"{$this->getName()}\": 'name' key is required when value is not a string";
1130 $type = $fileInfo[
'type'] ?? self::getPackageFileType( $fileInfo[
'name'] );
1131 $expanded = [
'type' =>
$type ];
1132 if ( !empty( $fileInfo[
'main'] ) ) {
1133 $mainFile = $fileInfo[
'name'];
1134 if (
$type !==
'script' ) {
1135 $msg = __METHOD__ .
": invalid package file definition for module " .
1136 "\"{$this->getName()}\": main file \"$mainFile\" must be of type \"script\", not \"$type\"";
1146 if ( isset( $fileInfo[
'content'] ) ) {
1147 $expanded[
'content'] = $fileInfo[
'content'];
1148 } elseif ( isset( $fileInfo[
'file'] ) ) {
1149 $expanded[
'filePath'] = $fileInfo[
'file'];
1150 } elseif ( isset( $fileInfo[
'callback'] ) ) {
1151 if ( !is_callable( $fileInfo[
'callback'] ) ) {
1152 $msg = __METHOD__ .
": invalid callback for package file \"{$fileInfo['name']}\"" .
1153 " in module \"{$this->getName()}\"";
1157 if ( isset( $fileInfo[
'versionCallback'] ) ) {
1158 if ( !is_callable( $fileInfo[
'versionCallback'] ) ) {
1159 throw new MWException( __METHOD__ .
": invalid versionCallback for file" .
1160 " \"{$fileInfo['name']}\" in module \"{$this->getName()}\"" );
1162 $expanded[
'definitionSummary'] =
1165 $expanded[
'callback'] = $fileInfo[
'callback'];
1167 $expanded[
'content'] = ( $fileInfo[
'callback'] )(
$context, $this->
getConfig() );
1169 } elseif ( isset( $fileInfo[
'config'] ) ) {
1170 if (
$type !==
'data' ) {
1171 $msg = __METHOD__ .
": invalid use of \"config\" for package file \"{$fileInfo['name']}\" " .
1172 "in module \"{$this->getName()}\": type must be \"data\" but is \"$type\"";
1176 $expandedConfig = [];
1177 foreach ( $fileInfo[
'config'] as $key => $var ) {
1178 $expandedConfig[ is_numeric( $key ) ? $var : $key ] = $this->
getConfig()->get( $var );
1180 $expanded[
'content'] = $expandedConfig;
1181 } elseif ( !empty( $fileInfo[
'main'] ) ) {
1183 $expanded[
'filePath'] = $fileInfo[
'name'];
1185 $msg = __METHOD__ .
": invalid package file definition for \"{$fileInfo['name']}\" " .
1186 "in module \"{$this->getName()}\": one of \"file\", \"content\", \"callback\" or \"config\" " .
1192 $expandedFiles[$fileInfo[
'name']] = $expanded;
1195 if ( $expandedFiles && $mainFile ===
null ) {
1197 foreach ( $expandedFiles as
$path => &
$file ) {
1198 if (
$file[
'type'] ===
'script' ) {
1206 'main' => $mainFile,
1207 'files' => $expandedFiles
1210 $this->expandedPackageFiles[$hash] = $result;
1220 if ( $this->packageFiles ===
null ) {
1229 if ( isset( $fileInfo[
'filePath'] ) ) {
1230 $localPath = $this->
getLocalPath( $fileInfo[
'filePath'] );
1231 if ( !file_exists( $localPath ) ) {
1232 $msg = __METHOD__ .
": package file not found: \"$localPath\"" .
1233 " in module \"{$this->getName()}\"";
1238 if ( $fileInfo[
'type'] ===
'data' ) {
1242 unset( $fileInfo[
'filePath'] );
1243 } elseif ( isset( $fileInfo[
'callback'] ) ) {
1244 $fileInfo[
'content'] = ( $fileInfo[
'callback'] )(
$context, $this->
getConfig() );
1245 unset( $fileInfo[
'callback'] );
1249 unset( $fileInfo[
'definitionSummary'] );
1266 if ( substr_compare(
"\xef\xbb\xbf", $input, 0, 3 ) === 0 ) {
1267 return substr( $input, 3 );
$wgResourceBasePath
The default 'remoteBasePath' value for instances of ResourceLoaderFileModule.
$wgExtensionAssetsPath
The URL path of the extensions directory.
$wgStylePath
The URL path of the skins directory.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
static getFileContentsHash( $filePaths, $algo='md4')
Get a hash of the combined contents of one or more files, either by retrieving a previously-computed ...
Context object that contains information about the state of a specific ResourceLoader web request.
Module based on local JavaScript/CSS files.
array $dependencies
List of modules this module depends on.
array $skinStyles
List of paths to CSS files to include when using specific skins.
getScriptFiles(ResourceLoaderContext $context)
Get a list of script file paths for this module, in order of proper execution.
getStyleFiles(ResourceLoaderContext $context)
Get a list of file paths for all styles in this module, in order of proper inclusion.
getTargets()
Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'].
getStyles(ResourceLoaderContext $context)
Get all styles for a given context.
array $skinScripts
List of JavaScript files to include when using a specific skin.
getFileHashes(ResourceLoaderContext $context)
Helper method for getDefinitionSummary.
readStyleFile( $path, $flip, ResourceLoaderContext $context)
Reads a style file.
string $skipFunction
File name containing the body of the skip function.
static getPackageFileType( $path)
Infer the file type from a package file path.
array $languageScripts
List of JavaScript files to include when using a specific language.
readScriptFiles(array $scripts)
Get the contents of a list of JavaScript files.
array $scripts
List of paths to JavaScript files to always include.
getSkipFunction()
Get the skip function.
getScriptURLsForDebug(ResourceLoaderContext $context)
getGroup()
Gets the name of the group this module should be loaded in.
getStyleURLsForDebug(ResourceLoaderContext $context)
bool $debugRaw
Link to raw files in debug mode.
array $templates
Saves a list of the templates named by the modules.
getTemplates()
Takes named templates by the module and returns an array mapping.
array $packageFiles
List of packaged files to make available through require()
static tryForKey(array $list, $key, $fallback=null)
Get a list of element that match a key, optionally using a fallback key.
string $localBasePath
Local base path, see __construct()
getMessages()
Gets list of message keys used by this module.
string $group
Name of group to load this module in.
compileLessFile( $fileName, ResourceLoaderContext $context)
Compile a LESS file into CSS.
expandPackageFiles(ResourceLoaderContext $context)
Internal helper for use by getPackageFiles(), getFileHashes() and getDefinitionSummary().
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
array $expandedPackageFiles
Expanded versions of $packageFiles, lazy-computed by expandPackageFiles(); keyed by context hash.
array $debugScripts
List of paths to JavaScript files to include in debug mode.
getSkinStyleFiles( $skinName)
Gets a list of file paths for all skin styles in the module used by the skin.
__construct( $options=[], $localBasePath=null, $remoteBasePath=null)
Constructs a new module from an options array.
getStyleSheetLang( $path)
Infer the stylesheet language from a stylesheet file path.
getDependencies(ResourceLoaderContext $context=null)
Gets list of names of modules this module depends on.
enableModuleContentVersion()
Disable module content versioning.
static extractBasePaths( $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
bool $hasGeneratedStyles
Whether getStyleURLsForDebug should return raw file paths, or return load.php urls.
getPackageFiles(ResourceLoaderContext $context)
Resolves the package files defintion and generates the content of each package file.
array $missingLocalFileRefs
Place where readStyleFile() tracks file dependencies for non-existent files.
static collateFilePathListByOption(array $list, $option, $default)
Collates file paths by option (where provided).
getScript(ResourceLoaderContext $context)
Gets all scripts for a given context concatenated together.
getType()
Get the module's load type.
stripBom( $input)
Takes an input string and removes the UTF-8 BOM character if present.
getAllSkinStyleFiles()
Gets a list of file paths for all skin style files in the module, for all available skins.
getFlip(ResourceLoaderContext $context)
Get whether CSS for this module should be flipped.
getLanguageScripts( $lang)
Get the set of language scripts for the given language, possibly using a fallback language.
array $styles
List of paths to CSS files to always include.
bool $noflip
Whether CSSJanus flipping should be skipped for this module.
getAllStyleFiles()
Returns all style files and all skin style files used by this module.
array $localFileRefs
Place where readStyleFile() tracks file dependencies.
array $messages
List of message keys used by this module.
string $remoteBasePath
Remote base path, see __construct()
readStyleFiles(array $styles, $flip, ResourceLoaderContext $context)
Get the contents of a list of CSS files.
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
getDeprecationInformation(ResourceLoaderContext $context=null)
Get JS representing deprecation information for the current module if available.
getFileDependencies(ResourceLoaderContext $context)
Get the files this module depends on indirectly for a given skin.
getMessageBlob(ResourceLoaderContext $context)
Get the hash of the message blob.
getLessVars(ResourceLoaderContext $context)
Get module-specific LESS variables, if any.
array $contents
Map of (context hash => cached module content)
saveFileDependencies(ResourceLoaderContext $context, $localFileRefs)
Set the files this module depends on indirectly for a given skin.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang