From b6d6d5670231cbf64f04f6fcc96ce0ea664f98bc Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sun, 9 Aug 2026 08:52:25 +0200 Subject: [PATCH] Hand resolved media files to mpdf in both path arguments mpdf used the resolved local file only when its basepathIsLocal flag was set. That flag is false whenever the baseurl host differs from the request host, which a non-default port is enough to cause, since HTTP_HOST carries the port while the parsed basepath host does not. Media was then fetched over HTTP again or, for the dw2pdf:// scheme, not at all, leaving exports without images and without an error message. Fixes a regression from 3c796bb. --- _test/DokuAssetFetcherTest.php | 107 +++++++++++++++++++++++++++++++++ src/DokuAssetFetcher.php | 13 +++- 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 _test/DokuAssetFetcherTest.php diff --git a/_test/DokuAssetFetcherTest.php b/_test/DokuAssetFetcherTest.php new file mode 100644 index 0000000..e6b5436 --- /dev/null +++ b/_test/DokuAssetFetcherTest.php @@ -0,0 +1,107 @@ +basepathIsLocal = $basepathIsLocal; + + return (new ReflectionProperty(Mpdf::class, 'assetFetcher'))->getValue($mpdf); + } + + /** + * @return array + */ + public static function fetchProvider(): array + { + global $conf; + + $media = DOKU_URL . 'lib/exe/fetch.php?media=wiki:dokuwiki-128.png'; + $mediaFile = $conf['mediadir'] . '/wiki/dokuwiki-128.png'; + $static = DOKU_URL . 'lib/images/throbber.gif'; + $staticFile = DOKU_INC . 'lib/images/throbber.gif'; + + return [ + 'fetch url, local basepath' => [$media, $mediaFile, true], + 'fetch url, remote basepath' => [$media, $mediaFile, false], + 'static file, local basepath' => [$static, $staticFile, true], + 'static file, remote basepath' => [$static, $staticFile, false], + ]; + } + + /** + * The local file has to be read regardless of the basepath setting. + * + * The second argument is the unmodified source as mpdf passes it for every image. + * + * @dataProvider fetchProvider + */ + public function testFetchDataFromPathReadsLocalFile(string $source, string $expected, bool $basepathIsLocal): void + { + $data = $this->getFetcher($basepathIsLocal)->fetchDataFromPath($source, $source); + + $this->assertSame(file_get_contents($expected), $data); + } + + /** + * The resolved file has to win over the source mpdf was originally given. + * + * With a local basepath mpdf prefers the second argument whenever it can open it. For a + * media URL that means an anonymous HTTP request back into the wiki instead of our local + * copy. A readable file stands in for such a loadable source here. + */ + public function testFetchDataFromPathIgnoresOriginalSource(): void + { + global $conf; + $file = $conf['mediadir'] . '/wiki/dokuwiki-128.png'; + $source = DOKU_URL . 'lib/exe/fetch.php?media=wiki:dokuwiki-128.png'; + $loadable = DOKU_INC . 'lib/images/throbber.gif'; + + $data = $this->getFetcher(true)->fetchDataFromPath($source, $loadable); + + $this->assertSame(file_get_contents($file), $data); + } + + /** + * Our pseudo scheme has no stream wrapper, so it can only ever be read locally. + * + * @testWith [true] + * [false] + */ + public function testFetchDataFromPathReadsDw2pdfScheme(bool $basepathIsLocal): void + { + global $conf; + $file = $conf['mediadir'] . '/wiki/dokuwiki-128.png'; + $source = 'dw2pdf://' . $file; + + $data = $this->getFetcher($basepathIsLocal)->fetchDataFromPath($source, $source); + + $this->assertSame(file_get_contents($file), $data); + } +} diff --git a/src/DokuAssetFetcher.php b/src/DokuAssetFetcher.php index 82d2688..3668ae0 100644 --- a/src/DokuAssetFetcher.php +++ b/src/DokuAssetFetcher.php @@ -9,10 +9,21 @@ */ class DokuAssetFetcher extends AssetFetcher { + /** + * Load the given asset, preferring a local copy of Dokuwiki media over an HTTP request + * + * Both arguments are overwritten with the resolved file, because mpdf picks either one + * depending on its basepathIsLocal flag. Leaving a URL in one of them would make mpdf + * fetch the asset over HTTP again. + * + * @param string $path Media reference or URL to load + * @param string|null $originalSrc The unmodified source as given in the HTML + * @return string The asset's binary data, empty when it could not be loaded + */ public function fetchDataFromPath($path, $originalSrc = null) { $resolved = (new MediaLinkResolver())->resolve($path); - if ($resolved) $originalSrc = $resolved['path']; + if ($resolved) $path = $originalSrc = $resolved['path']; return parent::fetchDataFromPath($path, $originalSrc); } }