201 $hasTemplates =
false;
204 list( $this->localBasePath, $this->remoteBasePath ) =
208 foreach ( $options as $member => $option ) {
215 $this->{$member} = is_array( $option ) ? $option : [ $option ];
218 $hasTemplates =
true;
219 $this->{$member} = is_array( $option ) ? $option : [ $option ];
222 case 'languageScripts':
225 if ( !is_array( $option ) ) {
226 throw new InvalidArgumentException(
227 "Invalid collated file path list error. " .
228 "'$option' given, array expected."
231 foreach ( $option as $key => $value ) {
232 if ( !is_string( $key ) ) {
233 throw new InvalidArgumentException(
234 "Invalid collated file path list key error. " .
235 "'$key' given, string expected."
238 $this->{$member}[$key] = is_array( $value ) ? $value : [ $value ];
242 $this->deprecated = $option;
249 $option = array_values( array_unique( (array)$option ) );
252 $this->{$member} = $option;
257 $this->{$member} = (string)$option;
262 $this->{$member} = (bool)$option;
266 if ( isset( $options[
'scripts'] ) && isset( $options[
'packageFiles'] ) ) {
267 throw new InvalidArgumentException(
"A module may not set both 'scripts' and 'packageFiles'" );
269 if ( isset( $options[
'packageFiles'] ) && isset( $options[
'skinScripts'] ) ) {
270 throw new InvalidArgumentException(
"Options 'skinScripts' and 'packageFiles' cannot be used together." );
272 if ( $hasTemplates ) {
273 $this->dependencies[] =
'mediawiki.template';
275 foreach ( $this->templates as $alias => $templatePath ) {
276 if ( is_int( $alias ) ) {
277 $alias = $this->
getPath( $templatePath );
279 $suffix = explode(
'.', $alias );
280 $suffix = end( $suffix );
281 $compilerModule =
'mediawiki.template.' . $suffix;
282 if ( $suffix !==
'html' && !in_array( $compilerModule, $this->dependencies ) ) {
283 $this->dependencies[] = $compilerModule;
317 if ( isset( $options[
'remoteExtPath'] ) ) {
322 if ( isset( $options[
'remoteSkinPath'] ) ) {
327 if ( array_key_exists(
'localBasePath', $options ) ) {
331 if ( array_key_exists(
'remoteBasePath', $options ) ) {
346 if ( $this->packageFiles !==
null ) {
349 if (
$file[
'type'] ===
'script+style' ) {
350 $file[
'content'] =
$file[
'content'][
'script'];
351 $file[
'type'] =
'script';
354 if ( $deprecationScript ) {
356 $mainFile[
'content'] = $deprecationScript . $mainFile[
'content'];
372 $urls[] = OutputPage::transformResourcePath(
401 if ( $this->packageFiles !==
null ) {
404 if (
$file[
'type'] ===
'script+style' ) {
406 $file[
'content'][
'style'],
407 $file[
'content'][
'styleLang'],
428 if ( $this->hasGeneratedStyles ) {
431 return parent::getStyleURLsForDebug( $context );
436 foreach ( $this->
getStyleFiles( $context ) as $mediaType => $list ) {
437 $urls[$mediaType] = [];
438 foreach ( $list as
$file ) {
439 $urls[$mediaType][] = OutputPage::transformResourcePath(
484 if ( !is_file( $localPath ) ) {
485 throw new RuntimeException(
486 __METHOD__ .
": $type file not found, or is not a file: \"$localPath\""
489 return $this->
stripBom( file_get_contents( $localPath ) );
498 if ( !$this->skipFunction ) {
501 $localPath = $this->
getLocalPath( $this->skipFunction );
527 foreach ( $styleFiles as $paths ) {
528 $files = array_merge( $files, $paths );
538 if ( isset( $fileInfo[
'filePath'] ) ) {
546 $files = array_merge(
551 $context->
getDebug() ? $this->debugScripts : [],
555 if ( $this->skipFunction ) {
560 $files = array_map( [ $this,
'getLocalPath' ], $files );
568 $files = array_unique( $files );
584 $summary = parent::getDefinitionSummary( $context );
606 $options[$member] = $this->{$member};
620 return $fileInfo[
'definitionSummary'] ?? ( $fileInfo[
'content'] ?? null );
625 'options' => $options,
633 $summary[] = [
'lessVars' => $lessVars ];
643 if ( $this->vueComponentParser ===
null ) {
655 return $path->getPath();
667 return $path->getLocalPath();
670 return "{$this->localBasePath}/$path";
679 return $path->getRemotePath();
682 return "{$this->remoteBasePath}/$path";
693 return preg_match(
'/\.less$/i',
$path ) ?
'less' :
'css';
702 if ( preg_match(
'/\.json$/i',
$path ) ) {
705 if ( preg_match(
'/\.vue$/i',
$path ) ) {
722 foreach ( $list as $key => $value ) {
723 if ( is_int( $key ) ) {
725 if ( !isset( $collatedFiles[$default] ) ) {
726 $collatedFiles[$default] = [];
728 $collatedFiles[$default][] = $value;
729 } elseif ( is_array( $value ) ) {
731 $optionValue = $value[$option] ?? $default;
732 if ( !isset( $collatedFiles[$optionValue] ) ) {
733 $collatedFiles[$optionValue] = [];
735 $collatedFiles[$optionValue][] = $key;
738 return $collatedFiles;
751 if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
769 $files = array_merge(
772 self::tryForKey( $this->skinScripts, $context->
getSkin(),
'default' )
775 $files = array_merge( $files, $this->debugScripts );
778 return array_unique( $files, SORT_REGULAR );
793 $fallbacks = MediaWikiServices::getInstance()->getLanguageFallback()
794 ->getAll(
$lang, LanguageFallback::MESSAGES );
795 foreach ( $fallbacks as
$lang ) {
813 return array_merge_recursive(
814 self::collateFilePathListByOption( $this->styles,
'media',
'all' ),
815 self::collateFilePathListByOption(
816 self::tryForKey( $this->skinStyles, $context->
getSkin(),
'default' ),
832 self::tryForKey( $this->skinStyles, $skinName ),
846 $internalSkinNames = array_keys( Skin::getSkinNames() );
847 $internalSkinNames[] =
'default';
849 foreach ( $internalSkinNames as $internalSkinName ) {
850 $styleFiles = array_merge_recursive(
865 $collatedStyleFiles = array_merge_recursive(
866 self::collateFilePathListByOption( $this->styles,
'media',
'all' ),
872 foreach ( $collatedStyleFiles as $media => $styleFiles ) {
873 foreach ( $styleFiles as $styleFile ) {
893 foreach ( array_unique(
$scripts, SORT_REGULAR ) as $fileName ) {
915 foreach (
$styles as $media => $files ) {
916 $uniqueFiles = array_unique( $files, SORT_REGULAR );
918 foreach ( $uniqueFiles as
$file ) {
921 $styles[$media] = implode(
"\n", $styleFiles );
965 if ( $styleLang ===
'less' ) {
967 $this->hasGeneratedStyles =
true;
970 if ( $this->
getFlip( $context ) ) {
971 $style = CSSJanus::transform(
978 $localDir = dirname( $localPath );
979 $remoteDir = dirname( $remotePath );
983 if ( file_exists(
$file ) ) {
984 $this->localFileRefs[] =
$file;
986 $this->missingLocalFileRefs[] =
$file;
1019 $canBeStylesOnly = !(
1022 || $this->debugScripts
1024 || $this->languageScripts
1025 || $this->skinScripts
1026 || $this->dependencies
1028 || $this->skipFunction
1031 return $canBeStylesOnly ? self::LOAD_STYLES : self::LOAD_GENERAL;
1071 $varsHash = hash(
'md4',
serialize( $vars ) );
1072 $styleHash = hash(
'md4', $style );
1073 $cacheKey =
$cache->makeGlobalKey(
'resourceloader-less', $styleHash, $varsHash );
1074 $cachedCompile =
$cache->get( $cacheKey );
1079 if ( isset( $cachedCompile[
'hash'] ) ) {
1081 if ( $contentHash === $cachedCompile[
'hash'] ) {
1082 $this->localFileRefs = array_merge( $this->localFileRefs, $cachedCompile[
'files'] );
1083 return $cachedCompile[
'css'];
1088 $css = $compiler->parse( $style, $fileName )->getCss();
1089 $files = $compiler->AllParsedFiles();
1090 $this->localFileRefs = array_merge( $this->localFileRefs, $files );
1092 $cache->set( $cacheKey, [
1096 ], $cache::TTL_DAY );
1109 foreach ( $this->templates as $alias => $templatePath ) {
1111 if ( is_int( $alias ) ) {
1112 $alias = $this->
getPath( $templatePath );
1143 if ( isset( $this->expandedPackageFiles[$hash] ) ) {
1144 return $this->expandedPackageFiles[$hash];
1146 if ( $this->packageFiles ===
null ) {
1149 $expandedFiles = [];
1152 foreach ( $this->packageFiles as $key => $fileInfo ) {
1153 if ( is_string( $fileInfo ) ) {
1154 $fileInfo = [
'name' => $fileInfo,
'file' => $fileInfo ];
1156 if ( !isset( $fileInfo[
'name'] ) ) {
1157 $msg =
"Missing 'name' key in package file info for module '{$this->getName()}'," .
1158 " offset '{$key}'.";
1160 throw new LogicException( $msg );
1162 $fileName = $fileInfo[
'name'];
1166 $expanded = [
'type' =>
$type ];
1167 if ( !empty( $fileInfo[
'main'] ) ) {
1168 $mainFile = $fileName;
1169 if (
$type !==
'script' &&
$type !==
'script-vue' ) {
1170 $msg =
"Main file in package must be of type 'script', module " .
1171 "'{$this->getName()}', main file '{$mainFile}' is '{$type}'.";
1173 throw new LogicException( $msg );
1181 if ( isset( $fileInfo[
'content'] ) ) {
1182 $expanded[
'content'] = $fileInfo[
'content'];
1183 } elseif ( isset( $fileInfo[
'file'] ) ) {
1184 $expanded[
'filePath'] = $fileInfo[
'file'];
1185 } elseif ( isset( $fileInfo[
'callback'] ) ) {
1187 $expanded[
'callbackParam'] = $fileInfo[
'callbackParam'] ??
null;
1189 if ( !is_callable( $fileInfo[
'callback'] ) ) {
1190 $msg =
"Invalid 'callback' for module '{$this->getName()}', file '{$fileName}'.";
1192 throw new LogicException( $msg );
1194 if ( isset( $fileInfo[
'versionCallback'] ) ) {
1195 if ( !is_callable( $fileInfo[
'versionCallback'] ) ) {
1196 throw new LogicException(
"Invalid 'versionCallback' for "
1197 .
"module '{$this->getName()}', file '{$fileName}'."
1203 $callbackResult = ( $fileInfo[
'versionCallback'] )(
1206 $expanded[
'callbackParam']
1209 $expanded[
'filePath'] = $callbackResult->getPath();
1211 $expanded[
'definitionSummary'] = $callbackResult;
1214 $expanded[
'callback'] = $fileInfo[
'callback'];
1217 $callbackResult = ( $fileInfo[
'callback'] )(
1220 $expanded[
'callbackParam']
1223 $expanded[
'filePath'] = $callbackResult->getPath();
1225 $expanded[
'content'] = $callbackResult;
1228 } elseif ( isset( $fileInfo[
'config'] ) ) {
1229 if (
$type !==
'data' ) {
1230 $msg =
"Key 'config' only valid for data files. "
1231 .
" Module '{$this->getName()}', file '{$fileName}' is '{$type}'.";
1233 throw new LogicException( $msg );
1235 $expandedConfig = [];
1236 foreach ( $fileInfo[
'config'] as $key => $var ) {
1237 $expandedConfig[ is_numeric( $key ) ? $var : $key ] = $this->
getConfig()->get( $var );
1239 $expanded[
'content'] = $expandedConfig;
1240 } elseif ( !empty( $fileInfo[
'main'] ) ) {
1242 $expanded[
'filePath'] = $fileName;
1244 $msg =
"Incomplete definition for module '{$this->getName()}', file '{$fileName}'. "
1245 .
"One of 'file', 'content', 'callback', or 'config' must be set.";
1247 throw new LogicException( $msg );
1250 $expandedFiles[$fileName] = $expanded;
1253 if ( $expandedFiles && $mainFile ===
null ) {
1255 foreach ( $expandedFiles as
$path =>
$file ) {
1256 if (
$file[
'type'] ===
'script' ||
$file[
'type'] ===
'script-vue' ) {
1264 'main' => $mainFile,
1265 'files' => $expandedFiles
1268 $this->expandedPackageFiles[$hash] = $result;
1279 if ( $this->packageFiles ===
null ) {
1283 if ( isset( $this->fullyExpandedPackageFiles[ $hash ] ) ) {
1284 return $this->fullyExpandedPackageFiles[ $hash ];
1294 if ( isset( $fileInfo[
'callback'] ) ) {
1295 $callbackResult = ( $fileInfo[
'callback'] )(
1298 $fileInfo[
'callbackParam']
1302 $fileInfo[
'filePath'] = $callbackResult->getPath();
1304 $fileInfo[
'content'] = $callbackResult;
1306 unset( $fileInfo[
'callback'] );
1313 if ( !isset( $fileInfo[
'content'] ) && isset( $fileInfo[
'filePath'] ) ) {
1314 $localPath = $this->
getLocalPath( $fileInfo[
'filePath'] );
1316 if ( $fileInfo[
'type'] ===
'data' ) {
1320 unset( $fileInfo[
'filePath'] );
1322 if ( $fileInfo[
'type'] ===
'script-vue' ) {
1325 $fileInfo[
'content'],
1326 [
'minifyTemplate' => !$context->
getDebug() ]
1328 }
catch ( Exception $e ) {
1329 $msg =
"Error parsing file '$fileName' in module '{$this->getName()}': " .
1332 throw new RuntimeException( $msg );
1334 $encodedTemplate = json_encode( $parsedComponent[
'template'] );
1338 $encodedTemplate = preg_replace(
'/(?<!\\\\)\\\\n/',
" \\\n", $encodedTemplate );
1340 $encodedTemplate = strtr( $encodedTemplate, [
"\\t" =>
"\t" ] );
1342 $fileInfo[
'content'] = [
1343 'script' => $parsedComponent[
'script'] .
1344 ";\nmodule.exports.template = $encodedTemplate;",
1345 'style' => $parsedComponent[
'style'] ??
'',
1346 'styleLang' => $parsedComponent[
'styleLang'] ??
'css'
1348 $fileInfo[
'type'] =
'script+style';
1352 unset( $fileInfo[
'definitionSummary'] );
1354 unset( $fileInfo[
'callbackParam'] );
1372 if ( substr_compare(
"\xef\xbb\xbf", $input, 0, 3 ) === 0 ) {
1373 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.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
static getLocalFileReferences( $source, $path)
Get a list of local files referenced in a stylesheet (includes non-existent files).
static remap( $source, $local, $remote, $embedData=true)
Remaps CSS URL paths and automatically embeds data URIs for CSS rules or url() values preceded by an ...
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.
getHash()
All factors that uniquely identify this request, except 'modules'.
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.
string $skipFunction
File name containing the body of the skip function.
static getPackageFileType( $path)
Infer the file type from a package file path.
__construct(array $options=[], $localBasePath=null, $remoteBasePath=null)
Constructs a new module from an options array.
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.
getFileContents( $localPath, $type)
Helper method for getting a file.
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.
readStyleFile( $path, ResourceLoaderContext $context)
Read and process a style file.
string $localBasePath
Local base path, see __construct()
compileLessString( $style, $fileName, ResourceLoaderContext $context)
Compile a LESS string into CSS.
getMessages()
Gets list of message keys used by this module.
string $group
Name of group to load this module in.
compileLessFile( $fileName, ResourceLoaderContext $context)
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.
readStyleFiles(array $styles, ResourceLoaderContext $context)
Get the contents of a list of CSS files.
getStyleSheetLang( $path)
Infer the stylesheet language from a stylesheet file path.
VueComponentParser null $vueComponentParser
Lazy-created by getVueComponentParser()
getDependencies(ResourceLoaderContext $context=null)
Gets list of names of modules this module depends on.
enableModuleContentVersion()
Disable module content versioning.
bool $hasGeneratedStyles
Whether getStyleURLsForDebug should return raw file paths, or return load.php urls.
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
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.
array $fullyExpandedPackageFiles
Further expanded versions of $expandedPackageFiles, lazy-computed by getPackageFiles(); keyed by cont...
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()
processStyle( $style, $styleLang, $path, ResourceLoaderContext $context)
Process a CSS/LESS string.
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.
getFileDependencies(ResourceLoaderContext $context)
Get the indirect dependencies for this module persuant to the skin/language context.
getMessageBlob(ResourceLoaderContext $context)
Get the hash of the message blob.
getDeprecationInformation(ResourceLoaderContext $context)
Get JS representing deprecation information for the current module if available.
getLessVars(ResourceLoaderContext $context)
Get module-specific LESS variables, if any.
array $contents
Map of (context hash => cached module content)
saveFileDependencies(ResourceLoaderContext $context, array $curFileRefs)
Save the indirect dependencies for this module persuant to the skin/language context.
Parser for Vue single file components (.vue files).
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang