MediaWiki  1.34.0
ResourceLoaderFileModule.php
Go to the documentation of this file.
1 <?php
36 
38  protected $localBasePath = '';
39 
41  protected $remoteBasePath = '';
42 
44  protected $templates = [];
45 
53  protected $scripts = [];
54 
62  protected $languageScripts = [];
63 
71  protected $skinScripts = [];
72 
80  protected $debugScripts = [];
81 
89  protected $styles = [];
90 
98  protected $skinStyles = [];
99 
107  protected $packageFiles = null;
108 
113  private $expandedPackageFiles = [];
114 
122  protected $dependencies = [];
123 
127  protected $skipFunction = null;
128 
136  protected $messages = [];
137 
139  protected $group;
140 
142  protected $debugRaw = true;
143 
144  protected $targets = [ 'desktop' ];
145 
147  protected $noflip = false;
148 
153  protected $hasGeneratedStyles = false;
154 
162  protected $localFileRefs = [];
163 
168  protected $missingLocalFileRefs = [];
169 
240  public function __construct(
241  $options = [],
242  $localBasePath = null,
243  $remoteBasePath = null
244  ) {
245  // Flag to decide whether to automagically add the mediawiki.template module
246  $hasTemplates = false;
247  // localBasePath and remoteBasePath both have unbelievably long fallback chains
248  // and need to be handled separately.
249  list( $this->localBasePath, $this->remoteBasePath ) =
251 
252  // Extract, validate and normalise remaining options
253  foreach ( $options as $member => $option ) {
254  switch ( $member ) {
255  // Lists of file paths
256  case 'scripts':
257  case 'debugScripts':
258  case 'styles':
259  case 'packageFiles':
260  $this->{$member} = is_array( $option ) ? $option : [ $option ];
261  break;
262  case 'templates':
263  $hasTemplates = true;
264  $this->{$member} = is_array( $option ) ? $option : [ $option ];
265  break;
266  // Collated lists of file paths
267  case 'languageScripts':
268  case 'skinScripts':
269  case 'skinStyles':
270  if ( !is_array( $option ) ) {
271  throw new InvalidArgumentException(
272  "Invalid collated file path list error. " .
273  "'$option' given, array expected."
274  );
275  }
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."
281  );
282  }
283  $this->{$member}[$key] = is_array( $value ) ? $value : [ $value ];
284  }
285  break;
286  case 'deprecated':
287  $this->deprecated = $option;
288  break;
289  // Lists of strings
290  case 'dependencies':
291  case 'messages':
292  case 'targets':
293  // Normalise
294  $option = array_values( array_unique( (array)$option ) );
295  sort( $option );
296 
297  $this->{$member} = $option;
298  break;
299  // Single strings
300  case 'group':
301  case 'skipFunction':
302  $this->{$member} = (string)$option;
303  break;
304  // Single booleans
305  case 'debugRaw':
306  case 'noflip':
307  $this->{$member} = (bool)$option;
308  break;
309  }
310  }
311  if ( isset( $options['scripts'] ) && isset( $options['packageFiles'] ) ) {
312  throw new InvalidArgumentException( "A module may not set both 'scripts' and 'packageFiles'" );
313  }
314  if ( $hasTemplates ) {
315  $this->dependencies[] = 'mediawiki.template';
316  // Ensure relevant template compiler module gets loaded
317  foreach ( $this->templates as $alias => $templatePath ) {
318  if ( is_int( $alias ) ) {
319  $alias = $this->getPath( $templatePath );
320  }
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;
326  }
327  }
328  }
329  }
330 
342  public static function extractBasePaths(
343  $options = [],
344  $localBasePath = null,
345  $remoteBasePath = null
346  ) {
347  global $IP, $wgResourceBasePath;
348 
349  // The different ways these checks are done, and their ordering, look very silly,
350  // but were preserved for backwards-compatibility just in case. Tread lightly.
351 
352  if ( $localBasePath === null ) {
354  }
355  if ( $remoteBasePath === null ) {
357  }
358 
359  if ( isset( $options['remoteExtPath'] ) ) {
360  global $wgExtensionAssetsPath;
361  $remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath'];
362  }
363 
364  if ( isset( $options['remoteSkinPath'] ) ) {
365  global $wgStylePath;
366  $remoteBasePath = $wgStylePath . '/' . $options['remoteSkinPath'];
367  }
368 
369  if ( array_key_exists( 'localBasePath', $options ) ) {
370  $localBasePath = (string)$options['localBasePath'];
371  }
372 
373  if ( array_key_exists( 'remoteBasePath', $options ) ) {
374  $remoteBasePath = (string)$options['remoteBasePath'];
375  }
376 
377  return [ $localBasePath, $remoteBasePath ];
378  }
379 
387  $deprecationScript = $this->getDeprecationInformation( $context );
388  if ( $this->packageFiles !== null ) {
389  $packageFiles = $this->getPackageFiles( $context );
390  if ( $deprecationScript ) {
391  $mainFile =& $packageFiles['files'][$packageFiles['main']];
392  $mainFile['content'] = $deprecationScript . $mainFile['content'];
393  }
394  return $packageFiles;
395  }
396 
397  $files = $this->getScriptFiles( $context );
398  return $deprecationScript . $this->readScriptFiles( $files );
399  }
400 
406  $urls = [];
407  foreach ( $this->getScriptFiles( $context ) as $file ) {
408  $urls[] = OutputPage::transformResourcePath(
409  $this->getConfig(),
410  $this->getRemotePath( $file )
411  );
412  }
413  return $urls;
414  }
415 
419  public function supportsURLLoading() {
420  // If package files are involved, don't support URL loading, because that breaks
421  // scoped require() functions
422  return $this->debugRaw && !$this->packageFiles;
423  }
424 
432  $styles = $this->readStyleFiles(
433  $this->getStyleFiles( $context ),
434  $this->getFlip( $context ),
435  $context
436  );
437  // Collect referenced files
438  $this->saveFileDependencies( $context, $this->localFileRefs );
439 
440  return $styles;
441  }
442 
448  if ( $this->hasGeneratedStyles ) {
449  // Do the default behaviour of returning a url back to load.php
450  // but with only=styles.
451  return parent::getStyleURLsForDebug( $context );
452  }
453  // Our module consists entirely of real css files,
454  // in debug mode we can load those directly.
455  $urls = [];
456  foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
457  $urls[$mediaType] = [];
458  foreach ( $list as $file ) {
459  $urls[$mediaType][] = OutputPage::transformResourcePath(
460  $this->getConfig(),
461  $this->getRemotePath( $file )
462  );
463  }
464  }
465  return $urls;
466  }
467 
473  public function getMessages() {
474  return $this->messages;
475  }
476 
482  public function getGroup() {
483  return $this->group;
484  }
485 
491  public function getDependencies( ResourceLoaderContext $context = null ) {
492  return $this->dependencies;
493  }
494 
500  public function getSkipFunction() {
501  if ( !$this->skipFunction ) {
502  return null;
503  }
504 
505  $localPath = $this->getLocalPath( $this->skipFunction );
506  if ( !file_exists( $localPath ) ) {
507  throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" );
508  }
509  $contents = $this->stripBom( file_get_contents( $localPath ) );
510  return $contents;
511  }
512 
521  public function enableModuleContentVersion() {
522  return false;
523  }
524 
533  $files = [];
534 
535  // Flatten style files into $files
536  $styles = self::collateFilePathListByOption( $this->styles, 'media', 'all' );
537  foreach ( $styles as $styleFiles ) {
538  $files = array_merge( $files, $styleFiles );
539  }
540 
542  self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
543  'media',
544  'all'
545  );
546  foreach ( $skinFiles as $styleFiles ) {
547  $files = array_merge( $files, $styleFiles );
548  }
549 
550  // Extract file names for package files
551  $expandedPackageFiles = $this->expandPackageFiles( $context );
553  array_filter( array_map( function ( $fileInfo ) {
554  return $fileInfo['filePath'] ?? null;
555  }, $expandedPackageFiles['files'] ) ) :
556  [];
557 
558  // Final merge, this should result in a master list of dependent files
559  $files = array_merge(
560  $files,
562  $this->scripts,
563  $this->templates,
564  $context->getDebug() ? $this->debugScripts : [],
565  $this->getLanguageScripts( $context->getLanguage() ),
566  self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
567  );
568  if ( $this->skipFunction ) {
569  $files[] = $this->skipFunction;
570  }
571  $files = array_map( [ $this, 'getLocalPath' ], $files );
572  // File deps need to be treated separately because they're already prefixed
573  $files = array_merge( $files, $this->getFileDependencies( $context ) );
574  // Filter out any duplicates from getFileDependencies() and others.
575  // Most commonly introduced by compileLessFile(), which always includes the
576  // entry point Less file we already know about.
577  $files = array_values( array_unique( $files ) );
578 
579  // Don't include keys or file paths here, only the hashes. Including that would needlessly
580  // cause global cache invalidation when files move or if e.g. the MediaWiki path changes.
581  // Any significant ordering is already detected by the definition summary.
582  return array_map( [ __CLASS__, 'safeFileHash' ], $files );
583  }
584 
592  $summary = parent::getDefinitionSummary( $context );
593 
594  $options = [];
595  foreach ( [
596  // The following properties are omitted because they don't affect the module reponse:
597  // - localBasePath (Per T104950; Changes when absolute directory name changes. If
598  // this affects 'scripts' and other file paths, getFileHashes accounts for that.)
599  // - remoteBasePath (Per T104950)
600  // - dependencies (provided via startup module)
601  // - targets
602  // - group (provided via startup module)
603  'scripts',
604  'debugScripts',
605  'styles',
606  'languageScripts',
607  'skinScripts',
608  'skinStyles',
609  'messages',
610  'templates',
611  'skipFunction',
612  'debugRaw',
613  ] as $member ) {
614  $options[$member] = $this->{$member};
615  }
616 
617  $packageFiles = $this->expandPackageFiles( $context );
618  if ( $packageFiles ) {
619  // Extract the minimum needed:
620  // - The 'main' pointer (included as-is).
621  // - The 'files' array, simplied to only which files exist (the keys of
622  // this array), and something that represents their non-file content.
623  // For packaged files that reflect files directly from disk, the
624  // 'getFileHashes' method tracks this already.
625  // It is important that the keys of the 'files' array are preserved,
626  // as they affect the module output.
627  $packageFiles['files'] = array_map( function ( $fileInfo ) {
628  return $fileInfo['definitionSummary'] ?? ( $fileInfo['content'] ?? null );
629  }, $packageFiles['files'] );
630  }
631 
632  $summary[] = [
633  'options' => $options,
634  'packageFiles' => $packageFiles,
635  'fileHashes' => $this->getFileHashes( $context ),
636  'messageBlob' => $this->getMessageBlob( $context ),
637  ];
638 
639  $lessVars = $this->getLessVars( $context );
640  if ( $lessVars ) {
641  $summary[] = [ 'lessVars' => $lessVars ];
642  }
643 
644  return $summary;
645  }
646 
651  protected function getPath( $path ) {
652  if ( $path instanceof ResourceLoaderFilePath ) {
653  return $path->getPath();
654  }
655 
656  return $path;
657  }
658 
663  protected function getLocalPath( $path ) {
664  if ( $path instanceof ResourceLoaderFilePath ) {
665  return $path->getLocalPath();
666  }
667 
668  return "{$this->localBasePath}/$path";
669  }
670 
675  protected function getRemotePath( $path ) {
676  if ( $path instanceof ResourceLoaderFilePath ) {
677  return $path->getRemotePath();
678  }
679 
680  return "{$this->remoteBasePath}/$path";
681  }
682 
690  public function getStyleSheetLang( $path ) {
691  return preg_match( '/\.less$/i', $path ) ? 'less' : 'css';
692  }
693 
699  public static function getPackageFileType( $path ) {
700  if ( preg_match( '/\.json$/i', $path ) ) {
701  return 'data';
702  }
703  return 'script';
704  }
705 
715  protected static function collateFilePathListByOption( array $list, $option, $default ) {
716  $collatedFiles = [];
717  foreach ( (array)$list as $key => $value ) {
718  if ( is_int( $key ) ) {
719  // File name as the value
720  if ( !isset( $collatedFiles[$default] ) ) {
721  $collatedFiles[$default] = [];
722  }
723  $collatedFiles[$default][] = $value;
724  } elseif ( is_array( $value ) ) {
725  // File name as the key, options array as the value
726  $optionValue = $value[$option] ?? $default;
727  if ( !isset( $collatedFiles[$optionValue] ) ) {
728  $collatedFiles[$optionValue] = [];
729  }
730  $collatedFiles[$optionValue][] = $key;
731  }
732  }
733  return $collatedFiles;
734  }
735 
745  protected static function tryForKey( array $list, $key, $fallback = null ) {
746  if ( isset( $list[$key] ) && is_array( $list[$key] ) ) {
747  return $list[$key];
748  } elseif ( is_string( $fallback )
749  && isset( $list[$fallback] )
750  && is_array( $list[$fallback] )
751  ) {
752  return $list[$fallback];
753  }
754  return [];
755  }
756 
764  $files = array_merge(
765  $this->scripts,
766  $this->getLanguageScripts( $context->getLanguage() ),
767  self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
768  );
769  if ( $context->getDebug() ) {
770  $files = array_merge( $files, $this->debugScripts );
771  }
772 
773  return array_unique( $files, SORT_REGULAR );
774  }
775 
783  private function getLanguageScripts( $lang ) {
784  $scripts = self::tryForKey( $this->languageScripts, $lang );
785  if ( $scripts ) {
786  return $scripts;
787  }
788  $fallbacks = Language::getFallbacksFor( $lang );
789  foreach ( $fallbacks as $lang ) {
790  $scripts = self::tryForKey( $this->languageScripts, $lang );
791  if ( $scripts ) {
792  return $scripts;
793  }
794  }
795 
796  return [];
797  }
798 
807  return array_merge_recursive(
808  self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
809  self::collateFilePathListByOption(
810  self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ),
811  'media',
812  'all'
813  )
814  );
815  }
816 
824  protected function getSkinStyleFiles( $skinName ) {
826  self::tryForKey( $this->skinStyles, $skinName ),
827  'media',
828  'all'
829  );
830  }
831 
838  protected function getAllSkinStyleFiles() {
839  $styleFiles = [];
840  $internalSkinNames = array_keys( Skin::getSkinNames() );
841  $internalSkinNames[] = 'default';
842 
843  foreach ( $internalSkinNames as $internalSkinName ) {
844  $styleFiles = array_merge_recursive(
845  $styleFiles,
846  $this->getSkinStyleFiles( $internalSkinName )
847  );
848  }
849 
850  return $styleFiles;
851  }
852 
858  public function getAllStyleFiles() {
859  $collatedStyleFiles = array_merge_recursive(
860  self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
861  $this->getAllSkinStyleFiles()
862  );
863 
864  $result = [];
865 
866  foreach ( $collatedStyleFiles as $media => $styleFiles ) {
867  foreach ( $styleFiles as $styleFile ) {
868  $result[] = $this->getLocalPath( $styleFile );
869  }
870  }
871 
872  return $result;
873  }
874 
882  private function readScriptFiles( array $scripts ) {
883  if ( empty( $scripts ) ) {
884  return '';
885  }
886  $js = '';
887  foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) {
888  $localPath = $this->getLocalPath( $fileName );
889  if ( !file_exists( $localPath ) ) {
890  throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
891  }
892  $contents = $this->stripBom( file_get_contents( $localPath ) );
893  $js .= $contents . "\n";
894  }
895  return $js;
896  }
897 
909  public function readStyleFiles( array $styles, $flip, ResourceLoaderContext $context ) {
910  if ( !$styles ) {
911  return [];
912  }
913  foreach ( $styles as $media => $files ) {
914  $uniqueFiles = array_unique( $files, SORT_REGULAR );
915  $styleFiles = [];
916  foreach ( $uniqueFiles as $file ) {
917  $styleFiles[] = $this->readStyleFile( $file, $flip, $context );
918  }
919  $styles[$media] = implode( "\n", $styleFiles );
920  }
921  return $styles;
922  }
923 
936  protected function readStyleFile( $path, $flip, ResourceLoaderContext $context ) {
937  $localPath = $this->getLocalPath( $path );
938  $remotePath = $this->getRemotePath( $path );
939  if ( !file_exists( $localPath ) ) {
940  $msg = __METHOD__ . ": style file not found: \"$localPath\"";
941  wfDebugLog( 'resourceloader', $msg );
942  throw new MWException( $msg );
943  }
944 
945  if ( $this->getStyleSheetLang( $localPath ) === 'less' ) {
946  $style = $this->compileLessFile( $localPath, $context );
947  $this->hasGeneratedStyles = true;
948  } else {
949  $style = $this->stripBom( file_get_contents( $localPath ) );
950  }
951 
952  if ( $flip ) {
953  $style = CSSJanus::transform( $style, true, false );
954  }
955  $localDir = dirname( $localPath );
956  $remoteDir = dirname( $remotePath );
957  // Get and register local file references
958  $localFileRefs = CSSMin::getLocalFileReferences( $style, $localDir );
959  foreach ( $localFileRefs as $file ) {
960  if ( file_exists( $file ) ) {
961  $this->localFileRefs[] = $file;
962  } else {
963  $this->missingLocalFileRefs[] = $file;
964  }
965  }
966  // Don't cache this call. remap() ensures data URIs embeds are up to date,
967  // and urls contain correct content hashes in their query string. (T128668)
968  return CSSMin::remap( $style, $localDir, $remoteDir, true );
969  }
970 
977  return $context->getDirection() === 'rtl' && !$this->noflip;
978  }
979 
985  public function getTargets() {
986  return $this->targets;
987  }
988 
995  public function getType() {
996  $canBeStylesOnly = !(
997  // All options except 'styles', 'skinStyles' and 'debugRaw'
998  $this->scripts
999  || $this->debugScripts
1000  || $this->templates
1001  || $this->languageScripts
1002  || $this->skinScripts
1003  || $this->dependencies
1004  || $this->messages
1005  || $this->skipFunction
1007  );
1008  return $canBeStylesOnly ? self::LOAD_STYLES : self::LOAD_GENERAL;
1009  }
1010 
1023  protected function compileLessFile( $fileName, ResourceLoaderContext $context ) {
1024  static $cache;
1025 
1026  if ( !$cache ) {
1028  }
1029 
1030  $vars = $this->getLessVars( $context );
1031  // Construct a cache key from the LESS file name, and a hash digest
1032  // of the LESS variables used for compilation.
1033  ksort( $vars );
1034  $varsHash = hash( 'md4', serialize( $vars ) );
1035  $cacheKey = $cache->makeGlobalKey( 'LESS', $fileName, $varsHash );
1036  $cachedCompile = $cache->get( $cacheKey );
1037 
1038  // If we got a cached value, we have to validate it by getting a
1039  // checksum of all the files that were loaded by the parser and
1040  // ensuring it matches the cached entry's.
1041  if ( isset( $cachedCompile['hash'] ) ) {
1042  $contentHash = FileContentsHasher::getFileContentsHash( $cachedCompile['files'] );
1043  if ( $contentHash === $cachedCompile['hash'] ) {
1044  $this->localFileRefs = array_merge( $this->localFileRefs, $cachedCompile['files'] );
1045  return $cachedCompile['css'];
1046  }
1047  }
1048 
1049  $compiler = $context->getResourceLoader()->getLessCompiler( $vars );
1050  $css = $compiler->parseFile( $fileName )->getCss();
1051  $files = $compiler->AllParsedFiles();
1052  $this->localFileRefs = array_merge( $this->localFileRefs, $files );
1053 
1054  // Cache for 24 hours (86400 seconds).
1055  $cache->set( $cacheKey, [
1056  'css' => $css,
1057  'files' => $files,
1058  'hash' => FileContentsHasher::getFileContentsHash( $files ),
1059  ], 3600 * 24 );
1060 
1061  return $css;
1062  }
1063 
1069  public function getTemplates() {
1070  $templates = [];
1071 
1072  foreach ( $this->templates as $alias => $templatePath ) {
1073  // Alias is optional
1074  if ( is_int( $alias ) ) {
1075  $alias = $this->getPath( $templatePath );
1076  }
1077  $localPath = $this->getLocalPath( $templatePath );
1078  if ( file_exists( $localPath ) ) {
1079  $content = file_get_contents( $localPath );
1080  $templates[$alias] = $this->stripBom( $content );
1081  } else {
1082  $msg = __METHOD__ . ": template file not found: \"$localPath\"";
1083  wfDebugLog( 'resourceloader', $msg );
1084  throw new MWException( $msg );
1085  }
1086  }
1087  return $templates;
1088  }
1089 
1109  $hash = $context->getHash();
1110  if ( isset( $this->expandedPackageFiles[$hash] ) ) {
1111  return $this->expandedPackageFiles[$hash];
1112  }
1113  if ( $this->packageFiles === null ) {
1114  return null;
1115  }
1116  $expandedFiles = [];
1117  $mainFile = null;
1118 
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";
1125  wfDebugLog( 'resourceloader', $msg );
1126  throw new MWException( $msg );
1127  }
1128 
1129  // Infer type from alias if needed
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\"";
1137  wfDebugLog( 'resourceloader', $msg );
1138  throw new MWException( $msg );
1139  }
1140  }
1141 
1142  // Perform expansions (except 'file' and 'callback'), creating one of these keys:
1143  // - 'content': literal value.
1144  // - 'filePath': content to be read from a file.
1145  // - 'callback': content computed by a callable.
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()}\"";
1154  wfDebugLog( 'resourceloader', $msg );
1155  throw new MWException( $msg );
1156  }
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()}\"" );
1161  }
1162  $expanded['definitionSummary'] =
1163  ( $fileInfo['versionCallback'] )( $context, $this->getConfig() );
1164  // Don't invoke 'callback' here as it may be expensive (T223260).
1165  $expanded['callback'] = $fileInfo['callback'];
1166  } else {
1167  $expanded['content'] = ( $fileInfo['callback'] )( $context, $this->getConfig() );
1168  }
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\"";
1173  wfDebugLog( 'resourceloader', $msg );
1174  throw new MWException( $msg );
1175  }
1176  $expandedConfig = [];
1177  foreach ( $fileInfo['config'] as $key => $var ) {
1178  $expandedConfig[ is_numeric( $key ) ? $var : $key ] = $this->getConfig()->get( $var );
1179  }
1180  $expanded['content'] = $expandedConfig;
1181  } elseif ( !empty( $fileInfo['main'] ) ) {
1182  // [ 'name' => 'foo.js', 'main' => true ] is shorthand
1183  $expanded['filePath'] = $fileInfo['name'];
1184  } else {
1185  $msg = __METHOD__ . ": invalid package file definition for \"{$fileInfo['name']}\" " .
1186  "in module \"{$this->getName()}\": one of \"file\", \"content\", \"callback\" or \"config\" " .
1187  "must be set";
1188  wfDebugLog( 'resourceloader', $msg );
1189  throw new MWException( $msg );
1190  }
1191 
1192  $expandedFiles[$fileInfo['name']] = $expanded;
1193  }
1194 
1195  if ( $expandedFiles && $mainFile === null ) {
1196  // The first package file that is a script is the main file
1197  foreach ( $expandedFiles as $path => &$file ) {
1198  if ( $file['type'] === 'script' ) {
1199  $mainFile = $path;
1200  break;
1201  }
1202  }
1203  }
1204 
1205  $result = [
1206  'main' => $mainFile,
1207  'files' => $expandedFiles
1208  ];
1209 
1210  $this->expandedPackageFiles[$hash] = $result;
1211  return $result;
1212  }
1213 
1220  if ( $this->packageFiles === null ) {
1221  return null;
1222  }
1223  $expandedPackageFiles = $this->expandPackageFiles( $context );
1224 
1225  // Expand file contents
1226  foreach ( $expandedPackageFiles['files'] as &$fileInfo ) {
1227  // Turn any 'filePath' or 'callback' key into actual 'content',
1228  // and remove the key after that.
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()}\"";
1234  wfDebugLog( 'resourceloader', $msg );
1235  throw new MWException( $msg );
1236  }
1237  $content = $this->stripBom( file_get_contents( $localPath ) );
1238  if ( $fileInfo['type'] === 'data' ) {
1239  $content = json_decode( $content );
1240  }
1241  $fileInfo['content'] = $content;
1242  unset( $fileInfo['filePath'] );
1243  } elseif ( isset( $fileInfo['callback'] ) ) {
1244  $fileInfo['content'] = ( $fileInfo['callback'] )( $context, $this->getConfig() );
1245  unset( $fileInfo['callback'] );
1246  }
1247 
1248  // Not needed for client response, exists for getDefinitionSummary().
1249  unset( $fileInfo['definitionSummary'] );
1250  }
1251 
1252  return $expandedPackageFiles;
1253  }
1254 
1265  protected function stripBom( $input ) {
1266  if ( substr_compare( "\xef\xbb\xbf", $input, 0, 3 ) === 0 ) {
1267  return substr( $input, 3 );
1268  }
1269  return $input;
1270  }
1271 }
ResourceLoaderFileModule\getSkipFunction
getSkipFunction()
Get the skip function.
Definition: ResourceLoaderFileModule.php:500
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:89
ResourceLoaderFileModule\$skinScripts
array $skinScripts
List of JavaScript files to include when using a specific skin.
Definition: ResourceLoaderFileModule.php:71
ResourceLoaderFileModule\$debugScripts
array $debugScripts
List of paths to JavaScript files to include in debug mode.
Definition: ResourceLoaderFileModule.php:80
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
ResourceLoaderFileModule\$templates
array $templates
Saves a list of the templates named by the modules.
Definition: ResourceLoaderFileModule.php:44
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:431
$fallback
$fallback
Definition: MessagesAb.php:11
ResourceLoaderFileModule\getFileHashes
getFileHashes(ResourceLoaderContext $context)
Helper method for getDefinitionSummary.
Definition: ResourceLoaderFileModule.php:532
ResourceLoaderModule\saveFileDependencies
saveFileDependencies(ResourceLoaderContext $context, $localFileRefs)
Set the files this module depends on indirectly for a given skin.
Definition: ResourceLoaderModule.php:453
ResourceLoaderFileModule\$noflip
bool $noflip
Whether CSSJanus flipping should be skipped for this module.
Definition: ResourceLoaderFileModule.php:147
ResourceLoaderFileModule\getScriptURLsForDebug
getScriptURLsForDebug(ResourceLoaderContext $context)
Definition: ResourceLoaderFileModule.php:405
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
ResourceLoaderFileModule\enableModuleContentVersion
enableModuleContentVersion()
Disable module content versioning.
Definition: ResourceLoaderFileModule.php:521
$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:142
$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:153
ResourceLoaderFileModule\$dependencies
array $dependencies
List of modules this module depends on.
Definition: ResourceLoaderFileModule.php:122
ResourceLoaderFileModule\$skinStyles
array $skinStyles
List of paths to CSS files to include when using specific skins.
Definition: ResourceLoaderFileModule.php:98
serialize
serialize()
Definition: ApiMessageTrait.php:138
ResourceLoaderFileModule\getFlip
getFlip(ResourceLoaderContext $context)
Get whether CSS for this module should be flipped.
Definition: ResourceLoaderFileModule.php:976
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1007
$wgStylePath
$wgStylePath
The URL path of the skins directory.
Definition: DefaultSettings.php:207
ResourceLoaderModule\getDeprecationInformation
getDeprecationInformation(ResourceLoaderContext $context=null)
Get JS representing deprecation information for the current module if available.
Definition: ResourceLoaderModule.php:142
ResourceLoaderFileModule\getDefinitionSummary
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
Definition: ResourceLoaderFileModule.php:591
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:89
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:671
ResourceLoaderFileModule
Module based on local JavaScript/CSS files.
Definition: ResourceLoaderFileModule.php:35
ResourceLoaderFileModule\readStyleFile
readStyleFile( $path, $flip, ResourceLoaderContext $context)
Reads a style file.
Definition: ResourceLoaderFileModule.php:936
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:482
ResourceLoaderFileModule\getSkinStyleFiles
getSkinStyleFiles( $skinName)
Gets a list of file paths for all skin styles in the module used by the skin.
Definition: ResourceLoaderFileModule.php:824
ResourceLoaderFileModule\compileLessFile
compileLessFile( $fileName, ResourceLoaderContext $context)
Compile a LESS file into CSS.
Definition: ResourceLoaderFileModule.php:1023
ResourceLoaderFileModule\getAllStyleFiles
getAllStyleFiles()
Returns all style files and all skin style files used by this module.
Definition: ResourceLoaderFileModule.php:858
$IP
$IP
Definition: update.php:3
ResourceLoaderFileModule\getStyleURLsForDebug
getStyleURLsForDebug(ResourceLoaderContext $context)
Definition: ResourceLoaderFileModule.php:447
ResourceLoaderFileModule\getRemotePath
getRemotePath( $path)
Definition: ResourceLoaderFileModule.php:675
ResourceLoaderFileModule\extractBasePaths
static extractBasePaths( $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
Definition: ResourceLoaderFileModule.php:342
ResourceLoaderFileModule\$targets
$targets
Definition: ResourceLoaderFileModule.php:144
ResourceLoaderFileModule\expandPackageFiles
expandPackageFiles(ResourceLoaderContext $context)
Internal helper for use by getPackageFiles(), getFileHashes() and getDefinitionSummary().
Definition: ResourceLoaderFileModule.php:1108
ResourceLoaderContext\getLanguage
getLanguage()
Definition: ResourceLoaderContext.php:166
ResourceLoaderFileModule\$messages
array $messages
List of message keys used by this module.
Definition: ResourceLoaderFileModule.php:136
ResourceLoaderModule\getMessageBlob
getMessageBlob(ResourceLoaderContext $context)
Get the hash of the message blob.
Definition: ResourceLoaderModule.php:559
ResourceLoaderFileModule\$languageScripts
array $languageScripts
List of JavaScript files to include when using a specific language.
Definition: ResourceLoaderFileModule.php:62
ResourceLoaderFileModule\stripBom
stripBom( $input)
Takes an input string and removes the UTF-8 BOM character if present.
Definition: ResourceLoaderFileModule.php:1265
ResourceLoaderFileModule\getPackageFiles
getPackageFiles(ResourceLoaderContext $context)
Resolves the package files defintion and generates the content of each package file.
Definition: ResourceLoaderFileModule.php:1219
$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:491
ResourceLoaderFileModule\getTargets
getTargets()
Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'].
Definition: ResourceLoaderFileModule.php:985
ResourceLoaderFileModule\$skipFunction
string $skipFunction
File name containing the body of the skip function.
Definition: ResourceLoaderFileModule.php:127
ResourceLoaderFileModule\$group
string $group
Name of group to load this module in.
Definition: ResourceLoaderFileModule.php:139
ResourceLoaderFileModule\$localFileRefs
array $localFileRefs
Place where readStyleFile() tracks file dependencies.
Definition: ResourceLoaderFileModule.php:162
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:882
ResourceLoaderFileModule\getPath
getPath( $path)
Definition: ResourceLoaderFileModule.php:651
ResourceLoaderFileModule\collateFilePathListByOption
static collateFilePathListByOption(array $list, $option, $default)
Collates file paths by option (where provided).
Definition: ResourceLoaderFileModule.php:715
ResourceLoaderFileModule\$expandedPackageFiles
array $expandedPackageFiles
Expanded versions of $packageFiles, lazy-computed by expandPackageFiles(); keyed by context hash.
Definition: ResourceLoaderFileModule.php:113
ResourceLoaderFileModule\__construct
__construct( $options=[], $localBasePath=null, $remoteBasePath=null)
Constructs a new module from an options array.
Definition: ResourceLoaderFileModule.php:240
ResourceLoaderFileModule\$localBasePath
string $localBasePath
Local base path, see __construct()
Definition: ResourceLoaderFileModule.php:38
$wgResourceBasePath
$wgResourceBasePath
The default 'remoteBasePath' value for instances of ResourceLoaderFileModule.
Definition: DefaultSettings.php:3688
$context
$context
Definition: load.php:45
ResourceLoaderFileModule\getScriptFiles
getScriptFiles(ResourceLoaderContext $context)
Get a list of script file paths for this module, in order of proper execution.
Definition: ResourceLoaderFileModule.php:763
ResourceLoaderFileModule\getStyleSheetLang
getStyleSheetLang( $path)
Infer the stylesheet language from a stylesheet file path.
Definition: ResourceLoaderFileModule.php:690
ResourceLoaderFileModule\getLocalPath
getLocalPath( $path)
Definition: ResourceLoaderFileModule.php:663
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:909
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:745
ResourceLoaderFileModule\getTemplates
getTemplates()
Takes named templates by the module and returns an array mapping.
Definition: ResourceLoaderFileModule.php:1069
$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:406
ResourceLoaderFileModule\$remoteBasePath
string $remoteBasePath
Remote base path, see __construct()
Definition: ResourceLoaderFileModule.php:41
ResourceLoaderFileModule\supportsURLLoading
supportsURLLoading()
Definition: ResourceLoaderFileModule.php:419
ResourceLoaderFileModule\getMessages
getMessages()
Gets list of message keys used by this module.
Definition: ResourceLoaderFileModule.php:473
ResourceLoaderFileModule\getAllSkinStyleFiles
getAllSkinStyleFiles()
Gets a list of file paths for all skin style files in the module, for all available skins.
Definition: ResourceLoaderFileModule.php:838
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:806
ResourceLoaderFileModule\getType
getType()
Get the module's load type.
Definition: ResourceLoaderFileModule.php:995
ResourceLoaderFileModule\getScript
getScript(ResourceLoaderContext $context)
Gets all scripts for a given context concatenated together.
Definition: ResourceLoaderFileModule.php:386
ResourceLoaderFileModule\$scripts
array $scripts
List of paths to JavaScript files to always include.
Definition: ResourceLoaderFileModule.php:53
ResourceLoaderFileModule\getLanguageScripts
getLanguageScripts( $lang)
Get the set of language scripts for the given language, possibly using a fallback language.
Definition: ResourceLoaderFileModule.php:783
ResourceLoaderModule\getConfig
getConfig()
Definition: ResourceLoaderModule.php:200
Language\getFallbacksFor
static getFallbacksFor( $code, $mode=self::MESSAGES_FALLBACKS)
Get the ordered list of fallback languages.
Definition: Language.php:4404
ResourceLoaderFileModule\$missingLocalFileRefs
array $missingLocalFileRefs
Place where readStyleFile() tracks file dependencies for non-existent files.
Definition: ResourceLoaderFileModule.php:168
ResourceLoaderFileModule\$packageFiles
array $packageFiles
List of packaged files to make available through require()
Definition: ResourceLoaderFileModule.php:107
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:268
ResourceLoaderFileModule\getPackageFileType
static getPackageFileType( $path)
Infer the file type from a package file path.
Definition: ResourceLoaderFileModule.php:699
$type
$type
Definition: testCompression.php:48