]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageStorage.php
Fix issue BookStackApp#5542 Sorting by name
[bookstack] / app / Uploads / ImageStorage.php
1 <?php
2
3 namespace BookStack\Uploads;
4
5 use Illuminate\Filesystem\FilesystemManager;
6 use Illuminate\Support\Str;
7
8 class ImageStorage
9 {
10     public function __construct(
11         protected FilesystemManager $fileSystem,
12     ) {
13     }
14
15     /**
16      * Get the storage disk for the given image type.
17      */
18     public function getDisk(string $imageType = ''): ImageStorageDisk
19     {
20         $diskName = $this->getDiskName($imageType);
21
22         return new ImageStorageDisk(
23             $diskName,
24             $this->fileSystem->disk($diskName),
25         );
26     }
27
28     /**
29      * Check if "local secure restricted" (Fetched behind auth, with permissions enforced)
30      * is currently active in the instance.
31      */
32     public function usingSecureRestrictedImages(): bool
33     {
34         return config('filesystems.images') === 'local_secure_restricted';
35     }
36
37     /**
38      * Clean up an image file name to be both URL and storage safe.
39      */
40     public function cleanImageFileName(string $name): string
41     {
42         $name = str_replace(' ', '-', $name);
43         $nameParts = explode('.', $name);
44         $extension = array_pop($nameParts);
45         $name = implode('-', $nameParts);
46         $name = Str::slug($name);
47
48         if (strlen($name) === 0) {
49             $name = Str::random(10);
50         }
51
52         return $name . '.' . $extension;
53     }
54
55     /**
56      * Get the name of the storage disk to use.
57      */
58     protected function getDiskName(string $imageType): string
59     {
60         $storageType = strtolower(config('filesystems.images'));
61         $localSecureInUse = ($storageType === 'local_secure' || $storageType === 'local_secure_restricted');
62
63         // Ensure system images (App logo) are uploaded to a public space
64         if ($imageType === 'system' && $localSecureInUse) {
65             return 'local';
66         }
67
68         // Rename local_secure options to get our image specific storage driver which
69         // is scoped to the relevant image directories.
70         if ($localSecureInUse) {
71             return 'local_secure_images';
72         }
73
74         return $storageType;
75     }
76
77     /**
78      * Get a storage path for the given image URL.
79      * Ensures the path will start with "uploads/images".
80      * Returns null if the url cannot be resolved to a local URL.
81      */
82     public function urlToPath(string $url): ?string
83     {
84         $url = ltrim(trim($url), '/');
85
86         // Handle potential relative paths
87         $isRelative = !str_starts_with($url, 'http');
88         if ($isRelative) {
89             if (str_starts_with(strtolower($url), 'uploads/images')) {
90                 return trim($url, '/');
91             }
92
93             return null;
94         }
95
96         // Handle local images based on paths on the same domain
97         $potentialHostPaths = [
98             url('uploads/images/'),
99             $this->getPublicUrl('/uploads/images/'),
100         ];
101
102         foreach ($potentialHostPaths as $potentialBasePath) {
103             $potentialBasePath = strtolower($potentialBasePath);
104             if (str_starts_with(strtolower($url), $potentialBasePath)) {
105                 return 'uploads/images/' . trim(substr($url, strlen($potentialBasePath)), '/');
106             }
107         }
108
109         return null;
110     }
111
112     /**
113      * Gets a public facing url for an image or location at the given path.
114      */
115     public static function getPublicUrl(string $filePath): string
116     {
117         return static::getPublicBaseUrl() . '/' . ltrim($filePath, '/');
118     }
119
120     /**
121      * Get the public base URL used for images.
122      * Will not include any path element of the image file, just the base part
123      * from where the path is then expected to start from.
124      * If s3-style store is in use it will default to guessing a public bucket URL.
125      */
126     protected static function getPublicBaseUrl(): string
127     {
128         $storageUrl = config('filesystems.url');
129
130         // Get the standard public s3 url if s3 is set as storage type
131         // Uses the nice, short URL if bucket name has no periods in otherwise the longer
132         // region-based url will be used to prevent http issues.
133         if (!$storageUrl && config('filesystems.images') === 's3') {
134             $storageDetails = config('filesystems.disks.s3');
135             if (!str_contains($storageDetails['bucket'], '.')) {
136                 $storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com';
137             } else {
138                 $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
139             }
140         }
141
142         $basePath = $storageUrl ?: url('/');
143
144         return rtrim($basePath, '/');
145     }
146 }