1 <?php namespace BookStack\Repos;
5 use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
6 use Intervention\Image\ImageManager as ImageTool;
7 use Illuminate\Contracts\Filesystem\Factory as FileSystem;
8 use Illuminate\Contracts\Cache\Repository as Cache;
10 use Symfony\Component\HttpFoundation\File\UploadedFile;
17 protected $fileSystem;
21 * @var FileSystemInstance
23 protected $storageInstance;
24 protected $storageUrl;
28 * ImageRepo constructor.
30 * @param ImageTool $imageTool
31 * @param FileSystem $fileSystem
34 public function __construct(Image $image, ImageTool $imageTool, FileSystem $fileSystem, Cache $cache)
36 $this->image = $image;
37 $this->imageTool = $imageTool;
38 $this->fileSystem = $fileSystem;
39 $this->cache = $cache;
44 * Get an image with the given id.
48 public function getById($id)
50 return $this->image->findOrFail($id);
55 * Get all images for the standard gallery view that's used for
56 * adding images to shared content such as pages.
58 * @param int $pageSize
61 public function getAllGallery($page = 0, $pageSize = 24)
63 $images = $this->image->where('type', '=', 'gallery')
64 ->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
65 $hasMore = count($images) > $pageSize;
67 $returnImages = $images->take(24);
68 $returnImages->each(function ($image) {
69 $this->loadThumbs($image);
73 'images' => $returnImages,
79 * Save a new image into storage and return the new image.
80 * @param UploadedFile $uploadFile
84 public function saveNew(UploadedFile $uploadFile, $type)
86 $storage = $this->getStorage();
87 $secureUploads = Setting::get('app-secure-images');
88 $imageName = str_replace(' ', '-', $uploadFile->getClientOriginalName());
90 if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
92 $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
93 while ($storage->exists($imagePath . $imageName)) {
94 $imageName = str_random(3) . $imageName;
96 $fullPath = $imagePath . $imageName;
98 $storage->put($fullPath, file_get_contents($uploadFile->getRealPath()));
100 $userId = auth()->user()->id;
101 $image = $this->image->forceCreate([
102 'name' => $imageName,
104 'url' => $this->getPublicUrl($fullPath),
106 'created_by' => $userId,
107 'updated_by' => $userId
110 $this->loadThumbs($image);
115 * Update the details of an image via an array of properties.
116 * @param Image $image
117 * @param array $updateDetails
120 public function updateImageDetails(Image $image, $updateDetails)
122 $image->fill($updateDetails);
124 $this->loadThumbs($image);
130 * Destroys an Image object along with its files and thumbnails.
131 * @param Image $image
134 public function destroyImage(Image $image)
136 $storage = $this->getStorage();
138 $imageFolder = dirname($image->path);
139 $imageFileName = basename($image->path);
140 $allImages = collect($storage->allFiles($imageFolder));
142 $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
143 $expectedIndex = strlen($imagePath) - strlen($imageFileName);
144 return strpos($imagePath, $imageFileName) === $expectedIndex;
147 $storage->delete($imagesToDelete->all());
149 // Cleanup of empty folders
150 foreach ($storage->directories($imageFolder) as $directory) {
151 if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory);
153 if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder);
160 * Check whether or not a folder is empty.
164 private function isFolderEmpty($path)
166 $files = $this->getStorage()->files($path);
167 $folders = $this->getStorage()->directories($path);
168 return count($files) === 0 && count($folders) === 0;
172 * Load thumbnails onto an image object.
173 * @param Image $image
175 private function loadThumbs(Image $image)
178 'gallery' => $this->getThumbnail($image, 150, 150),
179 'display' => $this->getThumbnail($image, 840, 0, true)
184 * Get the thumbnail for an image.
185 * If $keepRatio is true only the width will be used.
186 * Checks the cache then storage to avoid creating / accessing the filesystem on every check.
188 * @param Image $image
191 * @param bool $keepRatio
194 private function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
196 $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
197 $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
199 if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
200 return $this->getPublicUrl($thumbFilePath);
203 $storage = $this->getStorage();
205 if ($storage->exists($thumbFilePath)) {
206 return $this->getPublicUrl($thumbFilePath);
209 // Otherwise create the thumbnail
210 $thumb = $this->imageTool->make($storage->get($image->path));
212 $thumb->resize($width, null, function ($constraint) {
213 $constraint->aspectRatio();
214 $constraint->upsize();
217 $thumb->fit($width, $height);
220 $thumbData = (string)$thumb->encode();
221 $storage->put($thumbFilePath, $thumbData);
222 $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
224 return $this->getPublicUrl($thumbFilePath);
228 * Gets a public facing url for an image by checking relevant environment variables.
232 private function getPublicUrl($filePath)
234 if ($this->storageUrl === null) {
235 $storageUrl = env('STORAGE_URL');
237 // Get the standard public s3 url if s3 is set as storage type
238 if ($storageUrl == false && env('STORAGE_TYPE') === 's3') {
239 $storageDetails = config('filesystems.disks.s3');
240 $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'] . $filePath;
243 $this->storageUrl = $storageUrl;
246 return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath;
251 * Get the storage that will be used for storing images.
252 * @return FileSystemInstance
254 private function getStorage()
256 if ($this->storageInstance !== null) return $this->storageInstance;
258 $storageType = env('STORAGE_TYPE');
259 $this->storageInstance = $this->fileSystem->disk($storageType);
261 return $this->storageInstance;