Added test to cover.
use BookStack\References\ModelResolvers\ImageModelResolver;
use BookStack\References\ModelResolvers\PageLinkModelResolver;
use BookStack\References\ModelResolvers\PagePermalinkModelResolver;
+use BookStack\Uploads\ImageStorage;
class ZipReferenceParser
{
*/
public function parseLinks(string $content, callable $handler): string
{
- $escapedBase = preg_quote(url('/'), '/');
- $linkRegex = "/({$escapedBase}.*?)[\\t\\n\\f>\"'=?#()]/";
+ $linkRegex = $this->getLinkRegex();
$matches = [];
preg_match_all($linkRegex, $content, $matches);
return $this->modelResolvers;
}
+
+ /**
+ * Build the regex to identify links we should handle in content.
+ */
+ protected function getLinkRegex(): string
+ {
+ $urls = [rtrim(url('/'), '/')];
+ $imageUrl = rtrim(ImageStorage::getPublicUrl('/'), '/');
+ if ($urls[0] !== $imageUrl) {
+ $urls[] = $imageUrl;
+ }
+
+
+ $urlBaseRegex = implode('|', array_map(function ($url) {
+ return preg_quote($url, '/');
+ }, $urls));
+
+ return "/(({$urlBaseRegex}).*?)[\\t\\n\\f>\"'=?#()]/";
+ }
}
namespace BookStack\References\ModelResolvers;
use BookStack\Uploads\Image;
+use BookStack\Uploads\ImageStorage;
class ImageModelResolver implements CrossLinkModelResolver
{
+ protected ?string $pattern = null;
+
public function resolve(string $link): ?Image
{
- $pattern = '/^' . preg_quote(url('/uploads/images'), '/') . '\/(.+)/';
+ $pattern = $this->getUrlPattern();
$matches = [];
$match = preg_match($pattern, $link, $matches);
if (!$match) {
return null;
}
- $path = $matches[1];
+ $path = $matches[2];
// Strip thumbnail element from path if existing
$originalPathSplit = array_filter(explode('/', $path), function (string $part) {
return Image::query()->where('path', '=', $fullPath)->first();
}
+
+ /**
+ * Get the regex pattern to identify image URLs.
+ * Caches the pattern since it requires looking up to settings/config.
+ */
+ protected function getUrlPattern(): string
+ {
+ if ($this->pattern) {
+ return $this->pattern;
+ }
+
+ $urls = [url('/uploads/images')];
+ $baseImageUrl = ImageStorage::getPublicUrl('/uploads/images');
+ if ($baseImageUrl !== $urls[0]) {
+ $urls[] = $baseImageUrl;
+ }
+
+ $imageUrlRegex = implode('|', array_map(fn ($url) => preg_quote($url, '/'), $urls));
+ $this->pattern = '/^(' . $imageUrlRegex . ')\/(.+)/';
+
+ return $this->pattern;
+ }
}
}
/**
- * Gets a public facing url for an image by checking relevant environment variables.
+ * Gets a public facing url for an image or location at the given path.
+ */
+ public static function getPublicUrl(string $filePath): string
+ {
+ return static::getPublicBaseUrl() . '/' . ltrim($filePath, '/');
+ }
+
+ /**
+ * Get the public base URL used for images.
+ * Will not include any path element of the image file, just the base part
+ * from where the path is then expected to start from.
* If s3-style store is in use it will default to guessing a public bucket URL.
*/
- public function getPublicUrl(string $filePath): string
+ protected static function getPublicBaseUrl(): string
{
$storageUrl = config('filesystems.url');
$basePath = $storageUrl ?: url('/');
- return rtrim($basePath, '/') . $filePath;
+ return rtrim($basePath, '/');
}
}
$this->assertStringContainsString('href="[[bsexport:image:' . $image->id . ']]"', $chapterData['description_html']);
}
+ public function test_image_links_are_handled_when_using_external_storage_url()
+ {
+ $page = $this->entities->page();
+
+ $this->asEditor();
+ $this->files->uploadGalleryImageToPage($this, $page);
+ /** @var Image $image */
+ $image = Image::query()->where('type', '=', 'gallery')
+ ->where('uploaded_to', '=', $page->id)->first();
+
+ config()->set('filesystems.url', 'https://i.example.com/content');
+
+ $storageUrl = 'https://i.example.com/content/' . ltrim($image->path, '/');
+ $page->html = '<p><a href="' . $image->url . '">Original URL</a><a href="' . $storageUrl . '">Storage URL</a></p>';
+ $page->save();
+
+ $zipResp = $this->get($page->getUrl("/export/zip"));
+ $zip = $this->extractZipResponse($zipResp);
+ $pageData = $zip->data['page'];
+
+ $ref = '[[bsexport:image:' . $image->id . ']]';
+ $this->assertStringContainsString("<a href=\"{$ref}\">Original URL</a><a href=\"{$ref}\">Storage URL</a>", $pageData['html']);
+ }
+
public function test_cross_reference_links_external_to_export_are_not_converted()
{
$page = $this->entities->page();