]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageStorageDisk.php
Adapt tests with displayName array
[bookstack] / app / Uploads / ImageStorageDisk.php
1 <?php
2
3 namespace BookStack\Uploads;
4
5 use Illuminate\Contracts\Filesystem\Filesystem;
6 use Illuminate\Filesystem\FilesystemAdapter;
7 use League\Flysystem\WhitespacePathNormalizer;
8 use Symfony\Component\HttpFoundation\StreamedResponse;
9
10 class ImageStorageDisk
11 {
12     public function __construct(
13         protected string $diskName,
14         protected Filesystem $filesystem,
15     ) {
16     }
17
18     /**
19      * Check if local secure image storage (Fetched behind authentication)
20      * is currently active in the instance.
21      */
22     public function usingSecureImages(): bool
23     {
24         return $this->diskName === 'local_secure_images';
25     }
26
27     /**
28      * Change the originally provided path to fit any disk-specific requirements.
29      * This also ensures the path is kept to the expected root folders.
30      */
31     protected function adjustPathForDisk(string $path): string
32     {
33         $path = (new WhitespacePathNormalizer())->normalizePath(str_replace('uploads/images/', '', $path));
34
35         if ($this->usingSecureImages()) {
36             return $path;
37         }
38
39         return 'uploads/images/' . $path;
40     }
41
42     /**
43      * Check if a file at the given path exists.
44      */
45     public function exists(string $path): bool
46     {
47         return $this->filesystem->exists($this->adjustPathForDisk($path));
48     }
49
50     /**
51      * Get the file at the given path.
52      */
53     public function get(string $path): ?string
54     {
55         return $this->filesystem->get($this->adjustPathForDisk($path));
56     }
57
58     /**
59      * Save the given image data at the given path. Can choose to set
60      * the image as public which will update its visibility after saving.
61      */
62     public function put(string $path, string $data, bool $makePublic = false): void
63     {
64         $path = $this->adjustPathForDisk($path);
65         $this->filesystem->put($path, $data);
66
67         // Set visibility when a non-AWS-s3, s3-like storage option is in use.
68         // Done since this call can break s3-like services but desired for other image stores.
69         // Attempting to set ACL during above put request requires different permissions
70         // hence would technically be a breaking change for actual s3 usage.
71         if ($makePublic && !$this->isS3Like()) {
72             $this->filesystem->setVisibility($path, 'public');
73         }
74     }
75
76     /**
77      * Destroys an image at the given path.
78      * Searches for image thumbnails in addition to main provided path.
79      */
80     public function destroyAllMatchingNameFromPath(string $path): void
81     {
82         $path = $this->adjustPathForDisk($path);
83
84         $imageFolder = dirname($path);
85         $imageFileName = basename($path);
86         $allImages = collect($this->filesystem->allFiles($imageFolder));
87
88         // Delete image files
89         $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
90             return basename($imagePath) === $imageFileName;
91         });
92         $this->filesystem->delete($imagesToDelete->all());
93
94         // Cleanup of empty folders
95         $foldersInvolved = array_merge([$imageFolder], $this->filesystem->directories($imageFolder));
96         foreach ($foldersInvolved as $directory) {
97             if ($this->isFolderEmpty($directory)) {
98                 $this->filesystem->deleteDirectory($directory);
99             }
100         }
101     }
102
103     /**
104      * Get the mime type of the file at the given path.
105      * Only works for local filesystem adapters.
106      */
107     public function mimeType(string $path): string
108     {
109         $path = $this->adjustPathForDisk($path);
110         return $this->filesystem instanceof FilesystemAdapter ? $this->filesystem->mimeType($path) : '';
111     }
112
113     /**
114      * Get a stream response for the image at the given path.
115      */
116     public function response(string $path): StreamedResponse
117     {
118         return $this->filesystem->response($this->adjustPathForDisk($path));
119     }
120
121     /**
122      * Check if the image storage in use is an S3-like (but not likely S3) external system.
123      */
124     protected function isS3Like(): bool
125     {
126         $usingS3 = $this->diskName === 's3';
127         return $usingS3 && !is_null(config('filesystems.disks.s3.endpoint'));
128     }
129
130     /**
131      * Check whether a folder is empty.
132      */
133     protected function isFolderEmpty(string $path): bool
134     {
135         $files = $this->filesystem->files($path);
136         $folders = $this->filesystem->directories($path);
137
138         return count($files) === 0 && count($folders) === 0;
139     }
140 }