3 namespace BookStack\Uploads;
5 use Illuminate\Filesystem\FilesystemManager;
6 use Illuminate\Support\Str;
10 public function __construct(
11 protected FilesystemManager $fileSystem,
16 * Get the storage disk for the given image type.
18 public function getDisk(string $imageType = ''): ImageStorageDisk
20 $diskName = $this->getDiskName($imageType);
22 return new ImageStorageDisk(
24 $this->fileSystem->disk($diskName),
29 * Check if "local secure restricted" (Fetched behind auth, with permissions enforced)
30 * is currently active in the instance.
32 public function usingSecureRestrictedImages(): bool
34 return config('filesystems.images') === 'local_secure_restricted';
38 * Check if "local secure" (Fetched behind auth, either with or without permissions enforced)
39 * is currently active in the instance.
41 public function usingSecureImages(): bool
43 return config('filesystems.images') === 'local_secure' || $this->usingSecureRestrictedImages();
47 * Clean up an image file name to be both URL and storage safe.
49 public function cleanImageFileName(string $name): string
51 $name = str_replace(' ', '-', $name);
52 $nameParts = explode('.', $name);
53 $extension = array_pop($nameParts);
54 $name = implode('-', $nameParts);
55 $name = Str::slug($name);
57 if (strlen($name) === 0) {
58 $name = Str::random(10);
61 return $name . '.' . $extension;
65 * Get the name of the storage disk to use.
67 protected function getDiskName(string $imageType): string
69 $storageType = strtolower(config('filesystems.images'));
70 $localSecureInUse = ($storageType === 'local_secure' || $storageType === 'local_secure_restricted');
72 // Ensure system images (App logo) are uploaded to a public space
73 if ($imageType === 'system' && $localSecureInUse) {
77 // Rename local_secure options to get our image-specific storage driver, which
78 // is scoped to the relevant image directories.
79 if ($localSecureInUse) {
80 return 'local_secure_images';
87 * Get a storage path for the given image URL.
88 * Ensures the path will start with "uploads/images".
89 * Returns null if the url cannot be resolved to a local URL.
91 public function urlToPath(string $url): ?string
93 $url = ltrim(trim($url), '/');
95 // Handle potential relative paths
96 $isRelative = !str_starts_with($url, 'http');
98 if (str_starts_with(strtolower($url), 'uploads/images')) {
99 return trim($url, '/');
105 // Handle local images based on paths on the same domain
106 $potentialHostPaths = [
107 url('uploads/images/'),
108 $this->getPublicUrl('/uploads/images/'),
111 foreach ($potentialHostPaths as $potentialBasePath) {
112 $potentialBasePath = strtolower($potentialBasePath);
113 if (str_starts_with(strtolower($url), $potentialBasePath)) {
114 return 'uploads/images/' . trim(substr($url, strlen($potentialBasePath)), '/');
122 * Gets a public facing url for an image or location at the given path.
124 public static function getPublicUrl(string $filePath): string
126 return static::getPublicBaseUrl() . '/' . ltrim($filePath, '/');
130 * Get the public base URL used for images.
131 * Will not include any path element of the image file, just the base part
132 * from where the path is then expected to start from.
133 * If s3-style store is in use it will default to guessing a public bucket URL.
135 protected static function getPublicBaseUrl(): string
137 $storageUrl = config('filesystems.url');
139 // Get the standard public s3 url if s3 is set as storage type
140 // Uses the nice, short URL if bucket name has no periods in otherwise the longer
141 // region-based url will be used to prevent http issues.
142 if (!$storageUrl && config('filesystems.images') === 's3') {
143 $storageDetails = config('filesystems.disks.s3');
144 if (!str_contains($storageDetails['bucket'], '.')) {
145 $storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com';
147 $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
151 $basePath = $storageUrl ?: url('/');
153 return rtrim($basePath, '/');