]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageStorage.php
Merge pull request #5917 from BookStackApp/copy_references
[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      * Check if "local secure" (Fetched behind auth, either with or without permissions enforced)
39      * is currently active in the instance.
40      */
41     public function usingSecureImages(): bool
42     {
43         return config('filesystems.images') === 'local_secure' || $this->usingSecureRestrictedImages();
44     }
45
46     /**
47      * Clean up an image file name to be both URL and storage safe.
48      */
49     public function cleanImageFileName(string $name): string
50     {
51         $name = str_replace(' ', '-', $name);
52         $nameParts = explode('.', $name);
53         $extension = array_pop($nameParts);
54         $name = implode('-', $nameParts);
55         $name = Str::slug($name);
56
57         if (strlen($name) === 0) {
58             $name = Str::random(10);
59         }
60
61         return $name . '.' . $extension;
62     }
63
64     /**
65      * Get the name of the storage disk to use.
66      */
67     protected function getDiskName(string $imageType): string
68     {
69         $storageType = strtolower(config('filesystems.images'));
70         $localSecureInUse = ($storageType === 'local_secure' || $storageType === 'local_secure_restricted');
71
72         // Ensure system images (App logo) are uploaded to a public space
73         if ($imageType === 'system' && $localSecureInUse) {
74             return 'local';
75         }
76
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';
81         }
82
83         return $storageType;
84     }
85
86     /**
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.
90      */
91     public function urlToPath(string $url): ?string
92     {
93         $url = ltrim(trim($url), '/');
94
95         // Handle potential relative paths
96         $isRelative = !str_starts_with($url, 'http');
97         if ($isRelative) {
98             if (str_starts_with(strtolower($url), 'uploads/images')) {
99                 return trim($url, '/');
100             }
101
102             return null;
103         }
104
105         // Handle local images based on paths on the same domain
106         $potentialHostPaths = [
107             url('uploads/images/'),
108             $this->getPublicUrl('/uploads/images/'),
109         ];
110
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)), '/');
115             }
116         }
117
118         return null;
119     }
120
121     /**
122      * Gets a public facing url for an image or location at the given path.
123      */
124     public static function getPublicUrl(string $filePath): string
125     {
126         return static::getPublicBaseUrl() . '/' . ltrim($filePath, '/');
127     }
128
129     /**
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.
134      */
135     protected static function getPublicBaseUrl(): string
136     {
137         $storageUrl = config('filesystems.url');
138
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';
146             } else {
147                 $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
148             }
149         }
150
151         $basePath = $storageUrl ?: url('/');
152
153         return rtrim($basePath, '/');
154     }
155 }