NSFileRepo - Images can't be viewed within Media Viewer

Hi there,

we have an issue with the Media Viewer when using the NSFileRepo plugin. Though I’m not sure if it’s a bug or if it’s me having an issue with the configuration.

I’ve uploaded a new image (file) e.g. “PIMCORE:pimcore-logo.svg”.

  • When accessing the file page .../index.php/Datei:PIMCORE:Pimcore-Logo.svg the image on the file page is correctly embedded and visible.
  • The image is displayed correctly when clicking on Original File (on the file page) .../nsfr_img_auth.php/104/8/8d/Pimcore-Logo.svg
  • I get a “file does not exist” error when clicking on Open in Media Viewer (on the file page)
    • The URL which is beeing called is: .../index.php?title=PIMCORE:Übersicht#/media/Datei:Pimcore-Logo.svg The namespace part of the file name is missing

We use the following versions:

Mediawiki: 1.43.9
NSFileRepo: 3.0.11 (c6ca720), 08:40, 16. Jun. 2026

Has anyone an idea what the cause of the issue could be?

Thanks for your anwsers in advance. :slight_smile:
Peter

Hello and welcome to the Community Forums!

That’s a known incompatibility between Extension:NSFileRepo and Extension:MultimediaViewer. Thise patches should work:

extensions/MultimediaViewer/resources/mmv.bootstrap/mmv.bootstrap.js

diff --git a/resources/mmv.bootstrap/mmv.bootstrap.js b/resources/mmv.bootstrap/mmv.bootstrap.js
index 3abb67e1..1646af24 100644
--- a/resources/mmv.bootstrap/mmv.bootstrap.js
+++ b/resources/mmv.bootstrap/mmv.bootstrap.js
@@ -276,7 +276,7 @@ class MultimediaViewerBootstrap {
			// if this is a PDF filetype thumbnail, it can trick us,
			// so we short-circuit that logic and use the file page title
			// instead of the thumbnail logic.
-			title = mw.Title.newFromText( mw.config.get( 'wgTitle' ), mw.config.get( 'wgNamespaceNumber' ) );
+			title = mw.Title.makeTitle( mw.config.get( 'wgNamespaceNumber' ), mw.config.get( 'wgTitle' ) );
		} else {
			title = mw.Title.newFromImg( $thumb );
		}
@@ -329,7 +329,56 @@ class MultimediaViewerBootstrap {
			// no longer supported
			'[typeof*="mw:Image"]'
		);
-		const title = mw.Title.newFromImg( $thumb );
+
+		/**
+		 * nsfr_img_auth.php/3000/e/ea/Image.png
+		 * nsfr_img_auth.php/thumb/4/4c/Image.png/300px-Image.png
+		 * nsfr_img_auth.php/thumb/archive/3000/e/ea/20250326041929!Image.png/300px-Image.png
+		 */
+		const src = decodeURI( thumb.src );
+		const parts = src.split( '/' );
+
+		let title;
+		let isArchive;
+		let namespaceNumber;
+		let namespaceName;
+		let fileName;
+		let timestamp;
+
+		parts.forEach( ( part, index ) => {
+			if ( part === 'archive' ) {
+				isArchive = true;
+			} else if ( /^\d{4}$/.test( part ) ) {
+				namespaceNumber = part;
+			} else if ( part.length === 2 && parts[ index - 1 ].length === 1 ) {
+				// /e/ea/filename
+				fileName = decodeURIComponent( parts[ index + 1 ] );
+			}
+		} );
+
+		if ( isArchive && fileName && fileName.includes( '!' ) ) {
+			const archiveFileName = fileName.split( '!' );
+			timestamp = archiveFileName[ 0 ];
+			fileName = archiveFileName[ 1 ];
+		}
+
+		if ( namespaceNumber ) {
+			const formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' );
+			namespaceName = formattedNamespaces[ namespaceNumber ];
+			fileName = `${ namespaceName }:${ fileName }`;
+		}
+
+		// File = 6
+		title = mw.Title.makeTitle( 6, fileName );
+
+		if ( !title ) {
+			title = mw.Title.newFromImg( $thumb );
+		}
+
+		if ( isArchive && timestamp ) {
+			title.timestamp = timestamp;
+		}
+
		let caption;
 
		if ( !this.isValidExtension( title ) ) {

extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.ImageInfo.js

diff --git a/resources/mmv/provider/mmv.provider.ImageInfo.js b/resources/mmv/provider/mmv.provider.ImageInfo.js
index e8302b1f..445d071c 100644
--- a/resources/mmv/provider/mmv.provider.ImageInfo.js
+++ b/resources/mmv/provider/mmv.provider.ImageInfo.js
@@ -91,7 +91,7 @@ class ImageInfo extends Api {
	 * @return {jQuery.Promise} a promise which resolves to an Image object.
	 */
	get( file ) {
-		return this.getCachedPromise( file.getPrefixedDb(), () => this.apiGetWithMaxAge( {
+		const params = {
			formatversion: 2,
			action: 'query',
			prop: 'imageinfo',
@@ -100,7 +100,16 @@ class ImageInfo extends Api {
			iiextmetadatafilter: this.iiextmetadatafilter,
			iiextmetadatalanguage: this.options.language,
			uselang: 'content'
-		} ).then( ( data ) => this.getQueryPage( data ) ).then( ( page ) => {
+		};
+
+		if ( file.timestamp ) {
+			// To get the old image we have to reduce the timestamp and set the iistart for the imageinfo api.
+			params.iistart = file.timestamp -1;
+		}
+
+		return this.getCachedPromise( file.getPrefixedDb() + ( file.timestamp || '' ), () => this.apiGetWithMaxAge(
+			params
+		).then( ( data ) => this.getQueryPage( data ) ).then( ( page ) => {
			if ( page.imageinfo && page.imageinfo.length ) {
				return ImageModel.newFromImageInfo( file, page );
			} else if ( page.missing === true && page.imagerepository === '' ) {

extensions/MultimediaViewer/resources/mmv/provider/mmv.provider.ThumbnailInfo.js

diff --git a/resources/mmv/provider/mmv.provider.ThumbnailInfo.js b/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
index a3d742a3..0e698376 100644
--- a/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
+++ b/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
@@ -50,9 +50,9 @@ class ThumbnailInfo extends Api {
	get( file, sampleUrl, width, height ) {
		const match = sampleUrl.match( /(lang|page)([\d\-a-z]+)-(\d+)px/ ); // multi lingual SVG or PDF page
		const iiurlparam = match ? `${ match[ 1 ] }${ match[ 2 ] }-${ width }px` : undefined;
-		const cacheKey = [ file.getPrefixedDb(), width || '', height || '', iiurlparam || '' ].join();
+		const cacheKey = [ file.getPrefixedDb(), width || '', height || '', iiurlparam || '', file.timestamp || '' ].join();
 
-		return this.getCachedPromise( cacheKey, () => this.apiGetWithMaxAge( {
+		const params = {
			formatversion: 2,
			action: 'query',
			prop: 'imageinfo',
@@ -61,7 +61,15 @@ class ThumbnailInfo extends Api {
			iiurlparam,
			iiurlwidth: width,
			iiurlheight: height
-		} ).then( ( data ) => this.getQueryPage( data ) ).then( ( page ) => {
+		};
+
+		if ( file.timestamp ) {
+			params.iistart = file.timestamp - 1;
+		}
+
+		return this.getCachedPromise( cacheKey, () => this.apiGetWithMaxAge(
+			params
+		).then( ( data ) => this.getQueryPage( data ) ).then( ( page ) => {
			let imageInfo;
			if ( page.imageinfo && page.imageinfo[ 0 ] ) {
				imageInfo = page.imageinfo[ 0 ];

Actually, I don’t think all of them are required for this particular case. Some may be for better support of “archived image versions” (e.g. when using Extension:FlaggedRevs or Extension:ContentStabilization)

Hi @rvogel ,

thanks for the warm welcome and the patches! They work. :blush:

Sorry for the follow up question but there still seems to be in issue. I’ve embedded the photo within a Mediawiki page like this: [[Datei:PIMCORE:Pimcore-Logo.svg|mini]]. When I’m in viewing mode and clicking on the photo, the MediaViewer overlay opens and I still get the “file does not exists” error.

Do I need to apply another patch?

Can you maybe share the URL it is trying to fetch? Maybe you could provide some HAR file. See How to debug - BlueSpice Helpdesk for instructions.

I believe this may be related to the “thumb url”.

I would love to share the URL, but it’s just internal. How can I send you the HAR file?

Hello onlygecko,

from the error message “file does not exist: Datei:Pimcore-Logo.svg”, the error seems to happen in mmv.provider.ImageInfo.js - it is constructing a wrong file.getPrefixedDb(), which should be Datei:PIMCORE:Pimcore-Logo.svg. For further understanding the problem, you can:

  • Compare git diff in the MultimediaViewer directory with the message of rvogel, confirming whether all adjustments are there
  • Check the behavior of your wiki in the browser, and compare with the well working example below
  • If there is any information to share, but not suitable to publish on a public forum, support@hallowelt.com can be the email address to use

One more thing that worth noticing: in Reference:NSFileRepo - BlueSpice Helpdesk there are several config variables listed, where $egNSFileRepoNamespaceBlacklist by default exclude the id 104 you are using for the PIMCORE namespace - that used to be an old id reserved for SMW. Please remove 104 from the list, so that NSFR works with this namespace as expected.

Hope you get NSFR running well soon = )


For comparison of behavior, an working example is: Manual:Extension/PDFCreator/Custom cover sheets - BlueSpice Helpdesk

This is a page quoting [[File:Manual:PDFcreator book cover.png|alt=...|center|thumb|566x566px|Custom cover page]]. Inspecting the link of the displayed thumbnail picture, it is:

https://en.wiki.bluespice.com/w/nsfr_img_auth.php/thumb/3002/0/09/PDFcreator_book_cover.png/400px-PDFcreator_book_cover.png

When clicking into the MultiMedia Viewer view of this picture, the picture is served with its original link:

https://en.wiki.bluespice.com/w/nsfr_img_auth.php/3002/0/09/PDFcreator_book_cover.png

Hi @huaj1ng ,

thanks for the comprehensive reply!

I’ve removed the 104 namespace from the Blacklist - with no impact.
`$wgNSFileRepoNamespaceBlacklist=array ( 0 => 108, 1 => 274, 2 => 828, );`

The patch has been applied successfully. I did compare it to resources/mmv/provider/mmv.provider.ImageInfo.js - mediawiki/extensions/MultimediaViewer - Gitiles

You might be right with the guess, that file.getPrefixedDb() is the source of the issue.

file.getPrefixedDb() returns the following correct file name on the File page when clicking in “Open in Media Viewer”

On the “normal” page it returns the false file name when clicking on the image (to open in the Multimedia Viewer):

Does the info helps in any way to identify the cause of the issue?

Hello onlygecko,

thanks for the quick feedback! For the configuring line of NSFR: eg is used instead of wg as the configuration prefix. Actually, after testing, I noticed that the configuration is not trivial to override. Please use these config lines with a little magic instead of your original array line:

$GLOBALS['wgExtensionFunctions'][] = function () {
        $GLOBALS[ 'egNSFileRepoNamespaceBlacklist' ] = [ 108, 274, 828 ];
};

By the way, the two pictures in your previous message look identical to me - should there be one different picture instead?

Greetings,
Hua

Hello Hua,

using your config snippet did not work. But thanks for sticking with it (and me). That’s much appreciated!

You are absolutly right, I did post the same screenshot twice. So once again:

1.) A click on “Open in Multimedia Viewer” on File page

2.) A click on image on regular page.

As You said, `file.getPrefixedDb()\ might be worth having a look at. Do you have a suggestion where specificly I should look?

I would like add two things - which might not play a role but maybe they do…:

  • We run Mediawiki within a container behind a Apache Webserver. The Apache forwards the requests to the Mediawiki container.
  • I put a copy of nsfr_img_auth.php to the Mediawiki root directory and set $wgUploadPath = "/nsfr_img_auth.php"; Just reading the extension docu again I did not find any info that this needs to be done. Before the introduction of “NSFileRepo” the paramter was set to $wgUploadPath = "/img_auth.php"; Resetting it back to /img_auth.php did not change the behavior.