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;
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(
448 if ( $this->hasGeneratedStyles ) {
451 return parent::getStyleURLsForDebug(
$context );
456 foreach ( $this->
getStyleFiles( $context ) as $mediaType => $list ) {
457 $urls[$mediaType] = [];
458 foreach ( $list as
$file ) {
459 $urls[$mediaType][] = OutputPage::transformResourcePath(
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\"" );
537 foreach (
$styles as $styleFiles ) {
538 $files = array_merge( $files, $styleFiles );
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 ) {
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 );
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' ),
826 self::tryForKey( $this->skinStyles, $skinName ),
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 ) {
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 );
996 $canBeStylesOnly = !(
999 || $this->debugScripts
1001 || $this->languageScripts
1002 || $this->skinScripts
1003 || $this->dependencies
1005 || $this->skipFunction
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";
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 );