+ /**
+ * Expand the packageFiles definition into something that's (almost) the right format for
+ * getPackageFiles() to return. This expands shorthands, resolves config vars and callbacks,
+ * but does not expand file paths or read the actual contents of files. Those things are done
+ * by getPackageFiles().
+ *
+ * This is split up in this way so that getFileHashes() can get a list of file names, and
+ * getDefinitionSummary() can get config vars and callback results in their expanded form.
+ *
+ * @param ResourceLoaderContext $context
+ * @return array|null
+ */
+ private function expandPackageFiles( ResourceLoaderContext $context ) {
+ $hash = $context->getHash();
+ if ( isset( $this->expandedPackageFiles[$hash] ) ) {
+ return $this->expandedPackageFiles[$hash];
+ }
+ if ( $this->packageFiles === null ) {
+ return null;
+ }
+ $expandedFiles = [];
+ $mainFile = null;
+
+ foreach ( $this->packageFiles as $alias => $fileInfo ) {
+ if ( is_string( $fileInfo ) ) {
+ $fileInfo = [ 'name' => $fileInfo, 'file' => $fileInfo ];
+ } elseif ( !isset( $fileInfo['name'] ) ) {
+ $msg = __METHOD__ . ": invalid package file definition for module " .
+ "\"{$this->getName()}\": 'name' key is required when value is not a string";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+
+ // Infer type from alias if needed
+ $type = $fileInfo['type'] ?? self::getPackageFileType( $fileInfo['name'] );
+ $expanded = [ 'type' => $type ];
+ if ( !empty( $fileInfo['main'] ) ) {
+ $mainFile = $fileInfo['name'];
+ if ( $type !== 'script' ) {
+ $msg = __METHOD__ . ": invalid package file definition for module " .
+ "\"{$this->getName()}\": main file \"$mainFile\" must be of type \"script\", not \"$type\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ }
+
+ if ( isset( $fileInfo['content'] ) ) {
+ $expanded['content'] = $fileInfo['content'];
+ } elseif ( isset( $fileInfo['file'] ) ) {
+ $expanded['filePath'] = $fileInfo['file'];
+ } elseif ( isset( $fileInfo['callback'] ) ) {
+ if ( is_callable( $fileInfo['callback'] ) ) {
+ $expanded['content'] = $fileInfo['callback']( $context );
+ } else {
+ $msg = __METHOD__ . ": invalid callback for package file \"{$fileInfo['name']}\"" .
+ " in module \"{$this->getName()}\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ } elseif ( isset( $fileInfo['config'] ) ) {
+ if ( $type !== 'data' ) {
+ $msg = __METHOD__ . ": invalid use of \"config\" for package file \"{$fileInfo['name']}\" " .
+ "in module \"{$this->getName()}\": type must be \"data\" but is \"$type\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ $expandedConfig = [];
+ foreach ( $fileInfo['config'] as $key => $var ) {
+ $expandedConfig[ is_numeric( $key ) ? $var : $key ] = $this->getConfig()->get( $var );
+ }
+ $expanded['content'] = $expandedConfig;
+ } elseif ( !empty( $fileInfo['main'] ) ) {
+ // [ 'name' => 'foo.js', 'main' => true ] is shorthand
+ $expanded['filePath'] = $fileInfo['name'];
+ } else {
+ $msg = __METHOD__ . ": invalid package file definition for \"{$fileInfo['name']}\" " .
+ "in module \"{$this->getName()}\": one of \"file\", \"content\", \"callback\" or \"config\" " .
+ "must be set";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+
+ $expandedFiles[$fileInfo['name']] = $expanded;
+ }
+
+ if ( $expandedFiles && $mainFile === null ) {
+ // The first package file that is a script is the main file
+ foreach ( $expandedFiles as $path => &$file ) {
+ if ( $file['type'] === 'script' ) {
+ $mainFile = $path;
+ break;
+ }
+ }
+ }
+
+ $result = [
+ 'main' => $mainFile,
+ 'files' => $expandedFiles
+ ];
+
+ $this->expandedPackageFiles[$hash] = $result;
+ return $result;
+ }
+
+ /**
+ * Resolves the package files defintion and generates the content of each package file.
+ * @param ResourceLoaderContext $context
+ * @return array Package files data structure, see ResourceLoaderModule::getScript()
+ */
+ public function getPackageFiles( ResourceLoaderContext $context ) {
+ if ( $this->packageFiles === null ) {
+ return null;
+ }
+ $expandedPackageFiles = $this->expandPackageFiles( $context );
+
+ // Expand file contents
+ foreach ( $expandedPackageFiles['files'] as &$fileInfo ) {
+ if ( isset( $fileInfo['filePath'] ) ) {
+ $localPath = $this->getLocalPath( $fileInfo['filePath'] );
+ if ( !file_exists( $localPath ) ) {
+ $msg = __METHOD__ . ": package file not found: \"$localPath\"" .
+ " in module \"{$this->getName()}\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ $content = $this->stripBom( file_get_contents( $localPath ) );
+ if ( $fileInfo['type'] === 'data' ) {
+ $content = json_decode( $content );
+ }
+ $fileInfo['content'] = $content;
+ unset( $fileInfo['filePath'] );
+ }
+ }
+
+ return $expandedPackageFiles;
+ }
+