"wikimedia/cldr-plural-rule-parser": "1.0.0",
"wikimedia/composer-merge-plugin": "1.2.1",
"wikimedia/ip-set": "1.0.1",
+ "wikimedia/relpath": "1.0.3",
"wikimedia/utfnormal": "1.0.3",
"wikimedia/wrappedstring": "2.0.0",
"zordius/lightncandy": "0.21"
);
if ( !is_null( $deps ) ) {
- $this->fileDeps[$skin] = (array)FormatJson::decode( $deps, true );
+ $this->fileDeps[$skin] = self::expandRelativePaths(
+ (array)FormatJson::decode( $deps, true )
+ );
} else {
$this->fileDeps[$skin] = array();
}
* @param array $localFileRefs List of files
*/
protected function saveFileDependencies( $skin, $localFileRefs ) {
+ // Normalise array
+ $localFileRefs = array_values( array_unique( $localFileRefs ) );
+ sort( $localFileRefs );
+
try {
// If the list has been modified since last time we cached it, update the cache
if ( $localFileRefs !== $this->getFileDependencies( $skin ) ) {
array( array( 'md_module', 'md_skin' ) ), array(
'md_module' => $this->getName(),
'md_skin' => $skin,
- 'md_deps' => FormatJson::encode( $localFileRefs ),
+ // Use relative paths to avoid ghost entries when $IP changes (T111481)
+ 'md_deps' => FormatJson::encode( self::getRelativePaths( $localFileRefs ) ),
)
);
}
}
}
+ /**
+ * Make file paths relative to MediaWiki directory.
+ *
+ * This is used to make file paths safe for storing in a database without the paths
+ * becoming stale or incorrect when MediaWiki is moved or upgraded (T111481).
+ *
+ * @since 1.26
+ * @param array $filePaths
+ * @return array
+ */
+ protected static function getRelativePaths( Array $filePaths ) {
+ global $IP;
+ return array_map( function ( $path ) use ( $IP ) {
+ return RelPath\getRelativePath( $path, $IP );
+ }, $filePaths );
+ }
+
+ /**
+ * Expand directories relative to $IP.
+ *
+ * @since 1.26
+ * @param array $filePaths
+ * @return array
+ */
+ protected static function expandRelativePaths( Array $filePaths ) {
+ global $IP;
+ return array_map( function ( $path ) use ( $IP ) {
+ return RelPath\joinPath( $IP, $path );
+ }, $filePaths );
+ }
+
/**
* Get the last modification timestamp of the messages in this module for a given language.
* @param string $lang Language code
'Leave valid scripts as-is'
);
}
+
+ /**
+ * @covers ResourceLoaderModule::getRelativePaths
+ * @covers ResourceLoaderModule::expandRelativePaths
+ */
+ public function testPlaceholderize() {
+ $getRelativePaths = new ReflectionMethod( 'ResourceLoaderModule', 'getRelativePaths' );
+ $getRelativePaths->setAccessible( true );
+ $expandRelativePaths = new ReflectionMethod( 'ResourceLoaderModule', 'expandRelativePaths' );
+ $expandRelativePaths->setAccessible( true );
+
+ $this->setMwGlobals( array(
+ 'IP' => '/srv/example/mediawiki/core',
+ ) );
+ $raw = array(
+ '/srv/example/mediawiki/core/resources/foo.js',
+ '/srv/example/mediawiki/core/extensions/Example/modules/bar.js',
+ '/srv/example/mediawiki/skins/Example/baz.css',
+ '/srv/example/mediawiki/skins/Example/images/quux.png',
+ );
+ $canonical = array(
+ 'resources/foo.js',
+ 'extensions/Example/modules/bar.js',
+ '../skins/Example/baz.css',
+ '../skins/Example/images/quux.png',
+ );
+ $this->assertEquals(
+ $getRelativePaths->invoke( null, $raw ),
+ $canonical,
+ 'Insert placeholders'
+ );
+ $this->assertEquals(
+ $expandRelativePaths->invoke( null, $canonical ),
+ $raw,
+ 'Substitute placeholders'
+ );
+ }
}