MediaWiki  master
ResourceLoaderFileModule.php
Go to the documentation of this file.
1 <?php
25 
39 
41  protected $localBasePath = '';
42 
44  protected $remoteBasePath = '';
45 
47  protected $templates = [];
48 
56  protected $scripts = [];
57 
65  protected $languageScripts = [];
66 
74  protected $skinScripts = [];
75 
83  protected $debugScripts = [];
84 
92  protected $styles = [];
93 
101  protected $skinStyles = [];
102 
110  protected $packageFiles = null;
111 
116  private $expandedPackageFiles = [];
117 
125  protected $dependencies = [];
126 
130  protected $skipFunction = null;
131 
139  protected $messages = [];
140 
142  protected $group;
143 
145  protected $debugRaw = true;
146 
147  protected $targets = [ 'desktop' ];
148 
150  protected $noflip = false;
151 
156  protected $hasGeneratedStyles = false;
157 
165  protected $localFileRefs = [];
166 
171  protected $missingLocalFileRefs = [];
172 
243  public function __construct(
244  array $options = [],
245  $localBasePath = null,
246  $remoteBasePath = null
247  ) {
248  // Flag to decide whether to automagically add the mediawiki.template module
249  $hasTemplates = false;
250  // localBasePath and remoteBasePath both have unbelievably long fallback chains
251  // and need to be handled separately.
252  list( $this->localBasePath, $this->remoteBasePath ) =
254 
255  // Extract, validate and normalise remaining options
256  foreach ( $options as $member => $option ) {
257  switch ( $member ) {
258  // Lists of file paths
259  case 'scripts':
260  case 'debugScripts':
261  case 'styles':
262  case 'packageFiles':
263  $this->{$member} = is_array( $option ) ? $option : [ $option ];
264  break;
265  case 'templates':
266  $hasTemplates = true;
267  $this->{$member} = is_array( $option ) ? $option : [ $option ];
268  break;
269  // Collated lists of file paths
270  case 'languageScripts':
271  case 'skinScripts':
272  case 'skinStyles':
273  if ( !is_array( $option ) ) {
274  throw new InvalidArgumentException(
275  "Invalid collated file path list error. " .
276  "'$option' given, array expected."
277  );
278  }
279  foreach ( $option as $key => $value ) {
280  if ( !is_string( $key ) ) {
281  throw new InvalidArgumentException(
282  "Invalid collated file path list key error. " .
283  "'$key' given, string expected."
284  );
285  }
286  $this->{$member}[$key] = is_array( $value ) ? $value : [ $value ];
287  }
288  break;
289  case 'deprecated':
290  $this->deprecated = $option;
291  break;
292  // Lists of strings
293  case 'dependencies':
294  case 'messages':
295  case 'targets':
296  // Normalise
297  $option = array_values( array_unique( (array)$option ) );
298  sort( $option );
299 
300  $this->{$member} = $option;
301  break;
302  // Single strings
303  case 'group':
304  case 'skipFunction':
305  $this->{$member} = (string)$option;
306  break;
307  // Single booleans
308  case 'debugRaw':
309  case 'noflip':
310  $this->{$member} = (bool)$option;
311  break;
312  }
313  }
314  if ( isset( $options['scripts'] ) && isset( $options['packageFiles'] ) ) {
315  throw new InvalidArgumentException( "A module may not set both 'scripts' and 'packageFiles'" );
316  }
317  if ( $hasTemplates ) {
318  $this->dependencies[] = 'mediawiki.template';
319  // Ensure relevant template compiler module gets loaded
320  foreach ( $this->templates as $alias => $templatePath ) {
321  if ( is_int( $alias ) ) {
322  $alias = $this->getPath( $templatePath );
323  }
324  $suffix = explode( '.', $alias );
325  $suffix = end( $suffix );
326  $compilerModule = 'mediawiki.template.' . $suffix;
327  if ( $suffix !== 'html' && !in_array( $compilerModule, $this->dependencies ) ) {
328  $this->dependencies[] = $compilerModule;
329  }
330  }
331  }
332  }
333 
345  public static function extractBasePaths(
346  array $options = [],
347  $localBasePath = null,
348  $remoteBasePath = null
349  ) {
350  global $IP, $wgResourceBasePath;
351 
352  // The different ways these checks are done, and their ordering, look very silly,
353  // but were preserved for backwards-compatibility just in case. Tread lightly.
354 
355  if ( $localBasePath === null ) {
357  }
358  if ( $remoteBasePath === null ) {
360  }
361 
362  if ( isset( $options['remoteExtPath'] ) ) {
363  global $wgExtensionAssetsPath;
364  $remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
365  }
366 
367  if ( isset( $options['remoteSkinPath'] ) ) {
368  global $wgStylePath;
369  $remoteBasePath = $wgStylePath . '/' . $options['remoteSkinPath'];
370  }
371 
372  if ( array_key_exists( 'localBasePath', $options ) ) {
373  $localBasePath = (string)$options['localBasePath'];
374  }
375 
376  if ( array_key_exists( 'remoteBasePath', $options ) ) {
377  $remoteBasePath = (string)$options['remoteBasePath'];
378  }
379 
380  return [ $localBasePath, $remoteBasePath ];
381  }
382 
390  $deprecationScript = $this->getDeprecationInformation( $context );
391  if ( $this->packageFiles !== null ) {
392  $packageFiles = $this->getPackageFiles( $context );
393  if ( $deprecationScript ) {
394  $mainFile =& $packageFiles['files'][$packageFiles['main']];
395  $mainFile['content'] = $deprecationScript . $mainFile['content'];
396  }
397  return $packageFiles;
398  }
399 
400  $files = $this->getScriptFiles( $context );
401  return $deprecationScript . $this->readScriptFiles( $files );
402  }
403 
409  $urls = [];
410  foreach ( $this->getScriptFiles( $context ) as $file ) {
411  $urls[] = OutputPage::transformResourcePath(
412  $this->getConfig(),
413  $this->getRemotePath( $file )
414  );
415  }
416  return $urls;
417  }
418 
422  public function supportsURLLoading() {
423  // If package files are involved, don't support URL loading, because that breaks
424  // scoped require() functions
425  return $this->debugRaw && !$this->packageFiles;
426  }
427 
435  $styles = $this->readStyleFiles(
436  $this->getStyleFiles( $context ),
437  $this->getFlip( $context ),
438  $context
439  );
440  // Collect referenced files
441  $this->saveFileDependencies( $context, $this->localFileRefs );
442 
443  return $styles;
444  }
445 
451  if ( $this->hasGeneratedStyles ) {
452  // Do the default behaviour of returning a url back to load.php
453  // but with only=styles.
454  return parent::getStyleURLsForDebug( $context );
455  }
456  // Our module consists entirely of real css files,
457  // in debug mode we can load those directly.
458  $urls = [];
459  foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
460  $urls[$mediaType] = [];
461  foreach ( $list as $file ) {
462  $urls[$mediaType][] = OutputPage::transformResourcePath(
463  $this->getConfig(),
464  $this->getRemotePath( $file )
465  );
466  }
467  }
468  return $urls;
469  }
470 
476  public function getMessages() {
477  return $this->messages;
478  }
479 
485  public function getGroup() {
486  return $this->group;
487  }
488 
494  public function getDependencies( ResourceLoaderContext $context = null ) {
495  return $this->dependencies;
496  }
497 
503  public function getSkipFunction() {
504  if ( !$this->skipFunction ) {
505  return null;
506  }
507 
508  $localPath = $this->getLocalPath( $this->skipFunction );
509  if ( !file_exists( $localPath ) ) {
510  throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" );
511  }
512  $contents = $this->stripBom( file_get_contents( $localPath ) );
513  return $contents;
514  }
515 
524  public function enableModuleContentVersion() {
525  return false;
526  }
527 
536  $files = [];
537 
538  // Flatten style files into $files
539  $styles = self::collateFilePathListByOption( $this->styles, 'media', 'all' );
540  foreach ( $styles as $styleFiles ) {
541  $files = array_merge( $files, $styleFiles );
542  }
543 
545  self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
546  'media',
547  'all'
548  );
549  foreach ( $skinFiles as $styleFiles ) {
550  $files = array_merge( $files, $styleFiles );
551  }
552 
553  // Extract file paths for package files
554  // Optimisation: Use foreach() and isset() instead of array_map/array_filter.
555  // This is a hot code path, called by StartupModule for thousands of modules.
556  $expandedPackageFiles = $this->expandPackageFiles( $context );
557  $packageFiles = [];
558  if ( $expandedPackageFiles ) {
559  foreach ( $expandedPackageFiles['files'] as $fileInfo ) {
560  if ( isset( $fileInfo['filePath'] ) ) {
561  $packageFiles[] = $fileInfo['filePath'];
562  }
563  }
564  }
565 
566  // Merge all the file paths we were able discover directly from the module definition.
567  // This is the master list of direct-dependent files for this module.
568  $files = array_merge(
569  $files,
571  $this->scripts,
572  $this->templates,
573  $context->getDebug() ? $this->debugScripts : [],
574  $this->getLanguageScripts( $context->getLanguage() ),
575  self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
576  );
577  if ( $this->skipFunction ) {
578  $files[] = $this->skipFunction;
579  }
580 
581  // Expand these local paths into absolute file paths
582  $files = array_map( [ $this, 'getLocalPath' ], $files );
583 
584  // Add any lazily discovered file dependencies from previous module builds.
585  // These are added last because they are already absolute file paths.
586  $files = array_merge( $files, $this->getFileDependencies( $context ) );
587 
588  // Filter out any duplicates. Typically introduced by getFileDependencies() which
589  // may lazily re-discover a master file, and compileLessFile(), which always
590  // includes the entry point LESS file that we already have as a master file.
591  $files = array_unique( $files );
592 
593  // Don't return array keys or any other form of file path here, only the hashes.
594  // Including file paths would needlessly cause global cache invalidation when files
595  // move on disk or if e.g. the MediaWiki directory name changes.
596  // Anything where order is significant is already detected by the definition summary.
598  }
599 
607  $summary = parent::getDefinitionSummary( $context );
608 
609  $options = [];
610  foreach ( [
611  // The following properties are omitted because they don't affect the module reponse:
612  // - localBasePath (Per T104950; Changes when absolute directory name changes. If
613  // this affects 'scripts' and other file paths, getFileHashes accounts for that.)
614  // - remoteBasePath (Per T104950)
615  // - dependencies (provided via startup module)
616  // - targets
617  // - group (provided via startup module)
618  'scripts',
619  'debugScripts',
620  'styles',
621  'languageScripts',
622  'skinScripts',
623  'skinStyles',
624  'messages',
625  'templates',
626  'skipFunction',
627  'debugRaw',
628  ] as $member ) {
629  $options[$member] = $this->{$member};
630  }
631 
632  $packageFiles = $this->expandPackageFiles( $context );
633  if ( $packageFiles ) {
634  // Extract the minimum needed:
635  // - The 'main' pointer (included as-is).
636  // - The 'files' array, simplied to only which files exist (the keys of
637  // this array), and something that represents their non-file content.
638  // For packaged files that reflect files directly from disk, the
639  // 'getFileHashes' method tracks their content already.
640  // It is important that the keys of the $packageFiles['files'] array
641  // are preserved, as they do affect the module output.
642  $packageFiles['files'] = array_map( function ( $fileInfo ) {
643  return $fileInfo['definitionSummary'] ?? ( $fileInfo['content'] ?? null );
644  }, $packageFiles['files'] );
645  }
646 
647  $summary[] = [
648  'options' => $options,
649  'packageFiles' => $packageFiles,
650  'fileHashes' => $this->getFileHashes( $context ),
651  'messageBlob' => $this->getMessageBlob( $context ),
652  ];
653 
654  $lessVars = $this->getLessVars( $context );
655  if ( $lessVars ) {
656  $summary[] = [ 'lessVars' => $lessVars ];
657  }
658 
659  return $summary;
660  }
661 
666  protected function getPath( $path ) {
667  if ( $path instanceof ResourceLoaderFilePath ) {
668  return $path->getPath();
669  }
670 
671  return $path;
672  }
673 
678  protected function getLocalPath( $path ) {
679  if ( $path instanceof ResourceLoaderFilePath ) {
680  return $path->getLocalPath();
681  }
682 
683  return "{$this->localBasePath}/$path";
684  }
685 
690  protected function getRemotePath( $path ) {
691  if ( $path instanceof ResourceLoaderFilePath ) {
692  return $path->getRemotePath();
693  }
694 
695  return "{$this->remoteBasePath}/$path";
696  }
697 
705  public function getStyleSheetLang( $path ) {
706  return preg_match( '/\.less$/i', $path ) ? 'less' : 'css';
707  }
708 
714  public static function getPackageFileType( $path ) {
715  if ( preg_match( '/\.json$/i', $path ) ) {
716  return 'data';
717  }
718  return 'script';
719  }
720 
730  protected static function collateFilePathListByOption( array $list, $option, $default ) {
731  $collatedFiles = [];
732  foreach ( (array)$list as $key => $value ) {
733  if ( is_int( $key ) ) {
734  // File name as the value
735  if ( !isset( $collatedFiles[$default] ) ) {
736  $collatedFiles[$default] = [];
737  }
738  $collatedFiles[$default][] = $value;
739  } elseif ( is_array( $value ) ) {
740  // File name as the key, options array as the value
741  $optionValue = $value[$option] ?? $default;
742  if ( !isset( $collatedFiles[$optionValue] ) ) {
743  $collatedFiles[$optionValue] = [];
744  }
745  $collatedFiles[$optionValue][] = $key;
746  }
747  }
748  return $collatedFiles;
749  }
750 
760  protected static function tryForKey( array $list, $key, $fallback = null ) {
761  if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
762  return $list[$key];
763  } elseif ( is_string( $fallback )
764  && isset( $list[$fallback] )
765  && is_array( $list[$fallback] )
766  ) {
767  return $list[$fallback];
768  }
769  return [];
770  }
771 
779  $files = array_merge(
780  $this->scripts,
781  $this->getLanguageScripts( $context->getLanguage() ),
782  self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
783  );
784  if ( $context->getDebug() ) {
785  $files = array_merge( $files, $this->debugScripts );
786  }
787 
788  return array_unique( $files, SORT_REGULAR );
789  }
790 
798  private function getLanguageScripts( $lang ) {
799  $scripts = self::tryForKey( $this->languageScripts, $lang );
800  if ( $scripts ) {
801  return $scripts;
802  }
803  $fallbacks = MediaWikiServices::getInstance()->getLanguageFallback()
804  ->getAll( $lang, LanguageFallback::MESSAGES );
805  foreach ( $fallbacks as $lang ) {
806  $scripts = self::tryForKey( $this->languageScripts, $lang );
807  if ( $scripts ) {
808  return $scripts;
809  }
810  }
811 
812  return [];
813  }
814 
823  return array_merge_recursive(
824  self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
825  self::collateFilePathListByOption(
826  self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
827  'media',
828  'all'
829  )
830  );
831  }
832 
840  protected function getSkinStyleFiles( $skinName ) {
842  self::tryForKey( $this->skinStyles, $skinName ),
843  'media',
844  'all'
845  );
846  }
847 
854  protected function getAllSkinStyleFiles() {
855  $styleFiles = [];
856  $internalSkinNames = array_keys( Skin::getSkinNames() );
857  $internalSkinNames[] = 'default';
858 
859  foreach ( $internalSkinNames as $internalSkinName ) {
860  $styleFiles = array_merge_recursive(
861  $styleFiles,
862  $this->getSkinStyleFiles( $internalSkinName )
863  );
864  }
865 
866  return $styleFiles;
867  }
868 
874  public function getAllStyleFiles() {
875  $collatedStyleFiles = array_merge_recursive(
876  self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
877  $this->getAllSkinStyleFiles()
878  );
879 
880  $result = [];
881 
882  foreach ( $collatedStyleFiles as $media => $styleFiles ) {
883  foreach ( $styleFiles as $styleFile ) {
884  $result[] = $this->getLocalPath( $styleFile );
885  }
886  }
887 
888  return $result;
889  }
890 
898  private function readScriptFiles( array $scripts ) {
899  if ( empty( $scripts ) ) {
900  return '';
901  }
902  $js = '';
903  foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) {
904  $localPath = $this->getLocalPath( $fileName );
905  if ( !file_exists( $localPath ) ) {
906  throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
907  }
908  $contents = $this->stripBom( file_get_contents( $localPath ) );
909  $js .= $contents . "\n";
910  }
911  return $js;
912  }
913 
925  public function readStyleFiles( array $styles, $flip, ResourceLoaderContext $context ) {
926  if ( !$styles ) {
927  return [];
928  }
929  foreach ( $styles as $media => $files ) {
930  $uniqueFiles = array_unique( $files, SORT_REGULAR );
931  $styleFiles = [];
932  foreach ( $uniqueFiles as $file ) {
933  $styleFiles[] = $this->readStyleFile( $file, $flip, $context );
934  }
935  $styles[$media] = implode( "\n", $styleFiles );
936  }
937  return $styles;
938  }
939 
952  protected function readStyleFile( $path, $flip, ResourceLoaderContext $context ) {
953  $localPath = $this->getLocalPath( $path );
954  $remotePath = $this->getRemotePath( $path );
955  if ( !file_exists( $localPath ) ) {
956  throw new RuntimeException( "Style file not found: '{$localPath}'" );
957  }
958 
959  if ( $this->getStyleSheetLang( $localPath ) === 'less' ) {
960  $style = $this->compileLessFile( $localPath, $context );
961  $this->hasGeneratedStyles = true;
962  } else {
963  $style = $this->stripBom( file_get_contents( $localPath ) );
964  }
965 
966  if ( $flip ) {
967  $style = CSSJanus::transform( $style, true, false );
968  }
969  $localDir = dirname( $localPath );
970  $remoteDir = dirname( $remotePath );
971  // Get and register local file references
972  $localFileRefs = CSSMin::getLocalFileReferences( $style, $localDir );
973  foreach ( $localFileRefs as $file ) {
974  if ( file_exists( $file ) ) {
975  $this->localFileRefs[] = $file;
976  } else {
977  $this->missingLocalFileRefs[] = $file;
978  }
979  }
980  // Don't cache this call. remap() ensures data URIs embeds are up to date,
981  // and urls contain correct content hashes in their query string. (T128668)
982  return CSSMin::remap( $style, $localDir, $remoteDir, true );
983  }
984 
991  return $context->getDirection() === 'rtl' && !$this->noflip;
992  }
993 
999  public function getTargets() {
1000  return $this->targets;
1001  }
1002 
1009  public function getType() {
1010  $canBeStylesOnly = !(
1011  // All options except 'styles', 'skinStyles' and 'debugRaw'
1012  $this->scripts
1013  || $this->debugScripts
1014  || $this->templates
1015  || $this->languageScripts
1016  || $this->skinScripts
1017  || $this->dependencies
1018  || $this->messages
1019  || $this->skipFunction
1021  );
1022  return $canBeStylesOnly ? self::LOAD_STYLES : self::LOAD_GENERAL;
1023  }
1024 
1037  protected function compileLessFile( $fileName, ResourceLoaderContext $context ) {
1038  static $cache;
1039 
1040  if ( !$cache ) {
1042  }
1043 
1044  $vars = $this->getLessVars( $context );
1045  // Construct a cache key from the LESS file name, and a hash digest
1046  // of the LESS variables used for compilation.
1047  ksort( $vars );
1048  $varsHash = hash( 'md4', serialize( $vars ) );
1049  $cacheKey = $cache->makeGlobalKey( 'LESS', $fileName, $varsHash );
1050  $cachedCompile = $cache->get( $cacheKey );
1051 
1052  // If we got a cached value, we have to validate it by getting a
1053  // checksum of all the files that were loaded by the parser and
1054  // ensuring it matches the cached entry's.
1055  if ( isset( $cachedCompile['hash'] ) ) {
1056  $contentHash = FileContentsHasher::getFileContentsHash( $cachedCompile['files'] );
1057  if ( $contentHash === $cachedCompile['hash'] ) {
1058  $this->localFileRefs = array_merge( $this->localFileRefs, $cachedCompile['files'] );
1059  return $cachedCompile['css'];
1060  }
1061  }
1062 
1063  $compiler = $context->getResourceLoader()->getLessCompiler( $vars );
1064  $css = $compiler->parseFile( $fileName )->getCss();
1065  $files = $compiler->AllParsedFiles();
1066  $this->localFileRefs = array_merge( $this->localFileRefs, $files );
1067 
1068  // Cache for 24 hours (86400 seconds).
1069  $cache->set( $cacheKey, [
1070  'css' => $css,
1071  'files' => $files,
1072  'hash' => FileContentsHasher::getFileContentsHash( $files ),
1073  ], 3600 * 24 );
1074 
1075  return $css;
1076  }
1077 
1083  public function getTemplates() {
1084  $templates = [];
1085 
1086  foreach ( $this->templates as $alias => $templatePath ) {
1087  // Alias is optional
1088  if ( is_int( $alias ) ) {
1089  $alias = $this->getPath( $templatePath );
1090  }
1091  $localPath = $this->getLocalPath( $templatePath );
1092  if ( !file_exists( $localPath ) ) {
1093  throw new RuntimeException( "Template file not found: '{$localPath}'" );
1094  }
1095  $content = file_get_contents( $localPath );
1096  $templates[$alias] = $this->stripBom( $content );
1097  }
1098  return $templates;
1099  }
1100 
1121  $hash = $context->getHash();
1122  if ( isset( $this->expandedPackageFiles[$hash] ) ) {
1123  return $this->expandedPackageFiles[$hash];
1124  }
1125  if ( $this->packageFiles === null ) {
1126  return null;
1127  }
1128  $expandedFiles = [];
1129  $mainFile = null;
1130 
1131  foreach ( $this->packageFiles as $key => $fileInfo ) {
1132  if ( is_string( $fileInfo ) ) {
1133  $fileInfo = [ 'name' => $fileInfo, 'file' => $fileInfo ];
1134  }
1135  if ( !isset( $fileInfo['name'] ) ) {
1136  $msg = "Missing 'name' key in package file info for module '{$this->getName()}'," .
1137  " offset '{$key}'.";
1138  $this->getLogger()->error( $msg );
1139  throw new LogicException( $msg );
1140  }
1141  $fileName = $fileInfo['name'];
1142 
1143  // Infer type from alias if needed
1144  $type = $fileInfo['type'] ?? self::getPackageFileType( $fileName );
1145  $expanded = [ 'type' => $type ];
1146  if ( !empty( $fileInfo['main'] ) ) {
1147  $mainFile = $fileName;
1148  if ( $type !== 'script' ) {
1149  $msg = "Main file in package must be of type 'script', module " .
1150  "'{$this->getName()}', main file '{$mainFile}' is '{$type}'.";
1151  $this->getLogger()->error( $msg );
1152  throw new LogicException( $msg );
1153  }
1154  }
1155 
1156  // Perform expansions (except 'file' and 'callback'), creating one of these keys:
1157  // - 'content': literal value.
1158  // - 'filePath': content to be read from a file.
1159  // - 'callback': content computed by a callable.
1160  if ( isset( $fileInfo['content'] ) ) {
1161  $expanded['content'] = $fileInfo['content'];
1162  } elseif ( isset( $fileInfo['file'] ) ) {
1163  $expanded['filePath'] = $fileInfo['file'];
1164  } elseif ( isset( $fileInfo['callback'] ) ) {
1165  // If no extra parameter for the callback is given, use null.
1166  $expanded['callbackParam'] = $fileInfo['callbackParam'] ?? null;
1167 
1168  if ( !is_callable( $fileInfo['callback'] ) ) {
1169  $msg = "Invalid 'callback' for module '{$this->getName()}', file '{$fileName}'.";
1170  $this->getLogger()->error( $msg );
1171  throw new LogicException( $msg );
1172  }
1173  if ( isset( $fileInfo['versionCallback'] ) ) {
1174  if ( !is_callable( $fileInfo['versionCallback'] ) ) {
1175  throw new LogicException( "Invalid 'versionCallback' for "
1176  . "module '{$this->getName()}', file '{$fileName}'."
1177  );
1178  }
1179 
1180  // Execute the versionCallback with the same arguments that
1181  // would be given to the callback
1182  $expanded['definitionSummary'] = ( $fileInfo['versionCallback'] )(
1183  $context,
1184  $this->getConfig(),
1185  $expanded['callbackParam']
1186  );
1187  // Don't invoke 'callback' here as it may be expensive (T223260).
1188  $expanded['callback'] = $fileInfo['callback'];
1189  } else {
1190  // Else go ahead invoke callback with its arguments.
1191  $callbackResult = ( $fileInfo['callback'] )(
1192  $context,
1193  $this->getConfig(),
1194  $expanded['callbackParam']
1195  );
1196  if ( $callbackResult instanceof ResourceLoaderFilePath ) {
1197  $expanded['filePath'] = $callbackResult->getPath();
1198  } else {
1199  $expanded['content'] = $callbackResult;
1200  }
1201  }
1202  } elseif ( isset( $fileInfo['config'] ) ) {
1203  if ( $type !== 'data' ) {
1204  $msg = "Key 'config' only valid for data files. "
1205  . " Module '{$this->getName()}', file '{$fileName}' is '{$type}'.";
1206  $this->getLogger()->error( $msg );
1207  throw new LogicException( $msg );
1208  }
1209  $expandedConfig = [];
1210  foreach ( $fileInfo['config'] as $key => $var ) {
1211  $expandedConfig[ is_numeric( $key ) ? $var : $key ] = $this->getConfig()->get( $var );
1212  }
1213  $expanded['content'] = $expandedConfig;
1214  } elseif ( !empty( $fileInfo['main'] ) ) {
1215  // [ 'name' => 'foo.js', 'main' => true ] is shorthand
1216  $expanded['filePath'] = $fileName;
1217  } else {
1218  $msg = "Incomplete definition for module '{$this->getName()}', file '{$fileName}'. "
1219  . "One of 'file', 'content', 'callback', or 'config' must be set.";
1220  $this->getLogger()->error( $msg );
1221  throw new LogicException( $msg );
1222  }
1223 
1224  $expandedFiles[$fileName] = $expanded;
1225  }
1226 
1227  if ( $expandedFiles && $mainFile === null ) {
1228  // The first package file that is a script is the main file
1229  foreach ( $expandedFiles as $path => $file ) {
1230  if ( $file['type'] === 'script' ) {
1231  $mainFile = $path;
1232  break;
1233  }
1234  }
1235  }
1236 
1237  $result = [
1238  'main' => $mainFile,
1239  'files' => $expandedFiles
1240  ];
1241 
1242  $this->expandedPackageFiles[$hash] = $result;
1243  return $result;
1244  }
1245 
1253  if ( $this->packageFiles === null ) {
1254  return null;
1255  }
1256  $expandedPackageFiles = $this->expandPackageFiles( $context );
1257 
1258  // Expand file contents
1259  foreach ( $expandedPackageFiles['files'] as &$fileInfo ) {
1260  // Turn any 'filePath' or 'callback' key into actual 'content',
1261  // and remove the key after that. The callback could return a
1262  // ResourceLoaderFilePath object; if that happens, fall through
1263  // to the 'filePath' handling.
1264  if ( isset( $fileInfo['callback'] ) ) {
1265  $callbackResult = ( $fileInfo['callback'] )(
1266  $context,
1267  $this->getConfig(),
1268  $fileInfo['callbackParam']
1269  );
1270  if ( $callbackResult instanceof ResourceLoaderFilePath ) {
1271  // Fall through to the filePath handling code below
1272  $fileInfo['filePath'] = $callbackResult->getPath();
1273  } else {
1274  $fileInfo['content'] = $callbackResult;
1275  }
1276  unset( $fileInfo['callback'] );
1277  }
1278  if ( isset( $fileInfo['filePath'] ) ) {
1279  $localPath = $this->getLocalPath( $fileInfo['filePath'] );
1280  if ( !file_exists( $localPath ) ) {
1281  throw new RuntimeException( "Package file not found: '{$localPath}'" );
1282  }
1283  $content = $this->stripBom( file_get_contents( $localPath ) );
1284  if ( $fileInfo['type'] === 'data' ) {
1285  $content = json_decode( $content );
1286  }
1287  $fileInfo['content'] = $content;
1288  unset( $fileInfo['filePath'] );
1289  }
1290 
1291  // Not needed for client response, exists for use by getDefinitionSummary().
1292  unset( $fileInfo['definitionSummary'] );
1293  // Not needed for client response, used by callbacks only.
1294  unset( $fileInfo['callbackParam'] );
1295  }
1296 
1297  return $expandedPackageFiles;
1298  }
1299 
1310  protected function stripBom( $input ) {
1311  if ( substr_compare( "\xef\xbb\xbf", $input, 0, 3 ) === 0 ) {
1312  return substr( $input, 3 );
1313  }
1314  return $input;
1315  }
1316 }
ResourceLoaderFileModule\getSkipFunction
getSkipFunction()
Get the skip function.
Definition: ResourceLoaderFileModule.php:503
ResourceLoaderContext
Context object that contains information about the state of a specific ResourceLoader web request.
Definition: ResourceLoaderContext.php:33
ResourceLoaderFileModule\$styles
array $styles
List of paths to CSS files to always include.
Definition: ResourceLoaderFileModule.php:92
ResourceLoaderFileModule\$skinScripts
array $skinScripts
List of JavaScript files to include when using a specific skin.
Definition: ResourceLoaderFileModule.php:74
ResourceLoaderFileModule\$debugScripts
array $debugScripts
List of paths to JavaScript files to include in debug mode.
Definition: ResourceLoaderFileModule.php:83
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:35
ResourceLoaderFileModule\$templates
array $templates
Saves a list of the templates named by the modules.
Definition: ResourceLoaderFileModule.php:47
ResourceLoaderModule\$contents
array $contents
Map of (context hash => cached module content)
Definition: ResourceLoaderModule.php:64
ResourceLoaderFileModule\getStyles
getStyles(ResourceLoaderContext $context)
Get all styles for a given context.
Definition: ResourceLoaderFileModule.php:434
$fallback
$fallback
Definition: MessagesAb.php:11
ResourceLoaderFileModule\getFileHashes
getFileHashes(ResourceLoaderContext $context)
Helper method for getDefinitionSummary.
Definition: ResourceLoaderFileModule.php:535
ResourceLoaderFileModule\$noflip
bool $noflip
Whether CSSJanus flipping should be skipped for this module.
Definition: ResourceLoaderFileModule.php:150
ResourceLoaderFileModule\getScriptURLsForDebug
getScriptURLsForDebug(ResourceLoaderContext $context)
Definition: ResourceLoaderFileModule.php:408
ResourceLoaderFileModule\__construct
__construct(array $options=[], $localBasePath=null, $remoteBasePath=null)
Constructs a new module from an options array.
Definition: ResourceLoaderFileModule.php:243
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
MediaWiki\Languages\LanguageFallback
Definition: LanguageFallback.php:13
ResourceLoaderFileModule\enableModuleContentVersion
enableModuleContentVersion()
Disable module content versioning.
Definition: ResourceLoaderFileModule.php:524
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
ResourceLoaderFileModule\$debugRaw
bool $debugRaw
Link to raw files in debug mode.
Definition: ResourceLoaderFileModule.php:145
$wgExtensionAssetsPath
$wgExtensionAssetsPath
The URL path of the extensions directory.
Definition: DefaultSettings.php:222
ResourceLoaderFileModule\$hasGeneratedStyles
bool $hasGeneratedStyles
Whether getStyleURLsForDebug should return raw file paths, or return load.php urls.
Definition: ResourceLoaderFileModule.php:156
ResourceLoaderFileModule\$dependencies
array $dependencies
List of modules this module depends on.
Definition: ResourceLoaderFileModule.php:125
ResourceLoaderFileModule\$skinStyles
array $skinStyles
List of paths to CSS files to include when using specific skins.
Definition: ResourceLoaderFileModule.php:101
serialize
serialize()
Definition: ApiMessageTrait.php:138
ResourceLoaderFileModule\getFlip
getFlip(ResourceLoaderContext $context)
Get whether CSS for this module should be flipped.
Definition: ResourceLoaderFileModule.php:990
$wgStylePath
$wgStylePath
The URL path of the skins directory.
Definition: DefaultSettings.php:207
ResourceLoaderFileModule\getDefinitionSummary
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
Definition: ResourceLoaderFileModule.php:606
FileContentsHasher\getFileContentsHash
static getFileContentsHash( $filePaths, $algo='md4')
Get a hash of the combined contents of one or more files, either by retrieving a previously-computed ...
Definition: FileContentsHasher.php:88
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:57
ResourceLoaderModule\getLessVars
getLessVars(ResourceLoaderContext $context)
Get module-specific LESS variables, if any.
Definition: ResourceLoaderModule.php:665
ResourceLoaderFileModule
Module based on local JavaScript/CSS files.
Definition: ResourceLoaderFileModule.php:38
ResourceLoaderFileModule\readStyleFile
readStyleFile( $path, $flip, ResourceLoaderContext $context)
Reads a style file.
Definition: ResourceLoaderFileModule.php:952
MWException
MediaWiki exception.
Definition: MWException.php:26
ResourceLoaderFileModule\getGroup
getGroup()
Gets the name of the group this module should be loaded in.
Definition: ResourceLoaderFileModule.php:485
ResourceLoaderModule\getLogger
getLogger()
Definition: ResourceLoaderModule.php:223
ResourceLoaderFileModule\getSkinStyleFiles
getSkinStyleFiles( $skinName)
Gets a list of file paths for all skin styles in the module used by the skin.
Definition: ResourceLoaderFileModule.php:840
ResourceLoaderFileModule\compileLessFile
compileLessFile( $fileName, ResourceLoaderContext $context)
Compile a LESS file into CSS.
Definition: ResourceLoaderFileModule.php:1037
ResourceLoaderFileModule\getAllStyleFiles
getAllStyleFiles()
Returns all style files and all skin style files used by this module.
Definition: ResourceLoaderFileModule.php:874
ResourceLoaderFileModule\getStyleURLsForDebug
getStyleURLsForDebug(ResourceLoaderContext $context)
Definition: ResourceLoaderFileModule.php:450
ResourceLoaderFileModule\getRemotePath
getRemotePath( $path)
Definition: ResourceLoaderFileModule.php:690
ResourceLoaderModule\getDeprecationInformation
getDeprecationInformation(ResourceLoaderContext $context)
Get JS representing deprecation information for the current module if available.
Definition: ResourceLoaderModule.php:142
ResourceLoaderFileModule\$targets
$targets
Definition: ResourceLoaderFileModule.php:147
ResourceLoaderFileModule\expandPackageFiles
expandPackageFiles(ResourceLoaderContext $context)
Internal helper for use by getPackageFiles(), getFileHashes() and getDefinitionSummary().
Definition: ResourceLoaderFileModule.php:1120
ResourceLoaderContext\getLanguage
getLanguage()
Definition: ResourceLoaderContext.php:154
ResourceLoaderFileModule\$messages
array $messages
List of message keys used by this module.
Definition: ResourceLoaderFileModule.php:139
ResourceLoaderModule\getMessageBlob
getMessageBlob(ResourceLoaderContext $context)
Get the hash of the message blob.
Definition: ResourceLoaderModule.php:553
ResourceLoaderFileModule\$languageScripts
array $languageScripts
List of JavaScript files to include when using a specific language.
Definition: ResourceLoaderFileModule.php:65
ResourceLoaderFileModule\stripBom
stripBom( $input)
Takes an input string and removes the UTF-8 BOM character if present.
Definition: ResourceLoaderFileModule.php:1310
ResourceLoaderFileModule\extractBasePaths
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
Definition: ResourceLoaderFileModule.php:345
ResourceLoaderFileModule\getPackageFiles
getPackageFiles(ResourceLoaderContext $context)
Resolves the package files defintion and generates the content of each package file.
Definition: ResourceLoaderFileModule.php:1252
$content
$content
Definition: router.php:78
ResourceLoaderFileModule\getDependencies
getDependencies(ResourceLoaderContext $context=null)
Gets list of names of modules this module depends on.
Definition: ResourceLoaderFileModule.php:494
ResourceLoaderFileModule\getTargets
getTargets()
Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'].
Definition: ResourceLoaderFileModule.php:999
ResourceLoaderFileModule\$skipFunction
string $skipFunction
File name containing the body of the skip function.
Definition: ResourceLoaderFileModule.php:130
ResourceLoaderFileModule\$group
string $group
Name of group to load this module in.
Definition: ResourceLoaderFileModule.php:142
ResourceLoaderModule\saveFileDependencies
saveFileDependencies(ResourceLoaderContext $context, array $localFileRefs)
Set the files this module depends on indirectly for a given skin.
Definition: ResourceLoaderModule.php:447
ResourceLoaderFileModule\$localFileRefs
array $localFileRefs
Place where readStyleFile() tracks file dependencies.
Definition: ResourceLoaderFileModule.php:165
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:81
ResourceLoaderFileModule\readScriptFiles
readScriptFiles(array $scripts)
Get the contents of a list of JavaScript files.
Definition: ResourceLoaderFileModule.php:898
ResourceLoaderFileModule\getPath
getPath( $path)
Definition: ResourceLoaderFileModule.php:666
ResourceLoaderFileModule\collateFilePathListByOption
static collateFilePathListByOption(array $list, $option, $default)
Collates file paths by option (where provided).
Definition: ResourceLoaderFileModule.php:730
ResourceLoaderFileModule\$expandedPackageFiles
array $expandedPackageFiles
Expanded versions of $packageFiles, lazy-computed by expandPackageFiles(); keyed by context hash.
Definition: ResourceLoaderFileModule.php:116
ResourceLoaderFileModule\$localBasePath
string $localBasePath
Local base path, see __construct()
Definition: ResourceLoaderFileModule.php:41
$wgResourceBasePath
$wgResourceBasePath
The default 'remoteBasePath' value for instances of ResourceLoaderFileModule.
Definition: DefaultSettings.php:3693
$context
$context
Definition: load.php:40
ResourceLoaderFileModule\getScriptFiles
getScriptFiles(ResourceLoaderContext $context)
Get a list of script file paths for this module, in order of proper execution.
Definition: ResourceLoaderFileModule.php:778
ResourceLoaderFileModule\getStyleSheetLang
getStyleSheetLang( $path)
Infer the stylesheet language from a stylesheet file path.
Definition: ResourceLoaderFileModule.php:705
ResourceLoaderFileModule\getLocalPath
getLocalPath( $path)
Definition: ResourceLoaderFileModule.php:678
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:37
$cache
$cache
Definition: mcc.php:33
ResourceLoaderFileModule\readStyleFiles
readStyleFiles(array $styles, $flip, ResourceLoaderContext $context)
Get the contents of a list of CSS files.
Definition: ResourceLoaderFileModule.php:925
ResourceLoaderFileModule\tryForKey
static tryForKey(array $list, $key, $fallback=null)
Get a list of element that match a key, optionally using a fallback key.
Definition: ResourceLoaderFileModule.php:760
ResourceLoaderFileModule\getTemplates
getTemplates()
Takes named templates by the module and returns an array mapping.
Definition: ResourceLoaderFileModule.php:1083
$path
$path
Definition: NoLocalSettings.php:25
ResourceLoaderModule\getFileDependencies
getFileDependencies(ResourceLoaderContext $context)
Get the files this module depends on indirectly for a given skin.
Definition: ResourceLoaderModule.php:400
ResourceLoaderFileModule\$remoteBasePath
string $remoteBasePath
Remote base path, see __construct()
Definition: ResourceLoaderFileModule.php:44
ResourceLoaderFileModule\supportsURLLoading
supportsURLLoading()
Definition: ResourceLoaderFileModule.php:422
ResourceLoaderFileModule\getMessages
getMessages()
Gets list of message keys used by this module.
Definition: ResourceLoaderFileModule.php:476
$IP
$IP
Definition: WebStart.php:41
ResourceLoaderFileModule\getAllSkinStyleFiles
getAllSkinStyleFiles()
Gets a list of file paths for all skin style files in the module, for all available skins.
Definition: ResourceLoaderFileModule.php:854
ResourceLoaderFileModule\getStyleFiles
getStyleFiles(ResourceLoaderContext $context)
Get a list of file paths for all styles in this module, in order of proper inclusion.
Definition: ResourceLoaderFileModule.php:822
ResourceLoaderFileModule\getType
getType()
Get the module's load type.
Definition: ResourceLoaderFileModule.php:1009
ResourceLoaderFileModule\getScript
getScript(ResourceLoaderContext $context)
Gets all scripts for a given context concatenated together.
Definition: ResourceLoaderFileModule.php:389
ResourceLoaderFileModule\$scripts
array $scripts
List of paths to JavaScript files to always include.
Definition: ResourceLoaderFileModule.php:56
ResourceLoaderFileModule\getLanguageScripts
getLanguageScripts( $lang)
Get the set of language scripts for the given language, possibly using a fallback language.
Definition: ResourceLoaderFileModule.php:798
ResourceLoaderModule\getConfig
getConfig()
Definition: ResourceLoaderModule.php:194
ResourceLoaderFileModule\$missingLocalFileRefs
array $missingLocalFileRefs
Place where readStyleFile() tracks file dependencies for non-existent files.
Definition: ResourceLoaderFileModule.php:171
ResourceLoaderFileModule\$packageFiles
array $packageFiles
List of packaged files to make available through require()
Definition: ResourceLoaderFileModule.php:110
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:249
ResourceLoaderFileModule\getPackageFileType
static getPackageFileType( $path)
Infer the file type from a package file path.
Definition: ResourceLoaderFileModule.php:714
$type
$type
Definition: testCompression.php:50