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); } }