Dan Brown

Added an image service and facade, Cleaned Image Model

...@@ -7,23 +7,7 @@ use Illuminate\Database\Eloquent\Model; ...@@ -7,23 +7,7 @@ use Illuminate\Database\Eloquent\Model;
7 abstract class Entity extends Model 7 abstract class Entity extends Model
8 { 8 {
9 9
10 - /** 10 + use Ownable;
11 - * Relation for the user that created this entity.
12 - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
13 - */
14 - public function createdBy()
15 - {
16 - return $this->belongsTo('BookStack\User', 'created_by');
17 - }
18 -
19 - /**
20 - * Relation for the user that updated this entity.
21 - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
22 - */
23 - public function updatedBy()
24 - {
25 - return $this->belongsTo('BookStack\User', 'updated_by');
26 - }
27 11
28 /** 12 /**
29 * Compares this entity to another given entity. 13 * Compares this entity to another given entity.
......
...@@ -3,22 +3,22 @@ ...@@ -3,22 +3,22 @@
3 namespace BookStack; 3 namespace BookStack;
4 4
5 5
6 -class Image extends Entity 6 +use Images;
7 +
8 +class Image
7 { 9 {
10 + use Ownable;
8 11
9 protected $fillable = ['name']; 12 protected $fillable = ['name'];
10 13
11 - public function getFilePath()
12 - {
13 - return storage_path() . $this->url;
14 - }
15 -
16 /** 14 /**
17 - * Get the url for this item. 15 + * Get a thumbnail for this image.
18 - * @return string 16 + * @param int $width
17 + * @param int $height
18 + * @param bool|false $hardCrop
19 */ 19 */
20 - public function getUrl() 20 + public function getThumb($width, $height, $hardCrop = false)
21 { 21 {
22 - return public_path() . $this->url; 22 + Images::getThumbnail($this, $width, $height, $hardCrop);
23 } 23 }
24 } 24 }
......
1 +<?php namespace BookStack;
2 +
3 +
4 +trait Ownable
5 +{
6 + /**
7 + * Relation for the user that created this entity.
8 + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
9 + */
10 + public function createdBy()
11 + {
12 + return $this->belongsTo('BookStack\User', 'created_by');
13 + }
14 +
15 + /**
16 + * Relation for the user that updated this entity.
17 + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
18 + */
19 + public function updatedBy()
20 + {
21 + return $this->belongsTo('BookStack\User', 'updated_by');
22 + }
23 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 namespace BookStack\Providers; 3 namespace BookStack\Providers;
4 4
5 +use BookStack\Services\ImageService;
5 use BookStack\Services\ViewService; 6 use BookStack\Services\ViewService;
6 use Illuminate\Support\ServiceProvider; 7 use Illuminate\Support\ServiceProvider;
7 use BookStack\Services\ActivityService; 8 use BookStack\Services\ActivityService;
...@@ -40,5 +41,12 @@ class CustomFacadeProvider extends ServiceProvider ...@@ -40,5 +41,12 @@ class CustomFacadeProvider extends ServiceProvider
40 $this->app->make('Illuminate\Contracts\Cache\Repository') 41 $this->app->make('Illuminate\Contracts\Cache\Repository')
41 ); 42 );
42 }); 43 });
44 + $this->app->bind('images', function() {
45 + return new ImageService(
46 + $this->app->make('Intervention\Image\ImageManager'),
47 + $this->app->make('Illuminate\Contracts\Filesystem\Factory'),
48 + $this->app->make('Illuminate\Contracts\Cache\Repository')
49 + );
50 + });
43 } 51 }
44 } 52 }
......
...@@ -2,10 +2,7 @@ ...@@ -2,10 +2,7 @@
2 2
3 3
4 use BookStack\Image; 4 use BookStack\Image;
5 -use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance; 5 +use BookStack\Services\ImageService;
6 -use Intervention\Image\ImageManager as ImageTool;
7 -use Illuminate\Contracts\Filesystem\Factory as FileSystem;
8 -use Illuminate\Contracts\Cache\Repository as Cache;
9 use Setting; 6 use Setting;
10 use Symfony\Component\HttpFoundation\File\UploadedFile; 7 use Symfony\Component\HttpFoundation\File\UploadedFile;
11 8
...@@ -13,30 +10,17 @@ class ImageRepo ...@@ -13,30 +10,17 @@ class ImageRepo
13 { 10 {
14 11
15 protected $image; 12 protected $image;
16 - protected $imageTool; 13 + protected $imageService;
17 - protected $fileSystem;
18 - protected $cache;
19 -
20 - /**
21 - * @var FileSystemInstance
22 - */
23 - protected $storageInstance;
24 - protected $storageUrl;
25 -
26 14
27 /** 15 /**
28 * ImageRepo constructor. 16 * ImageRepo constructor.
29 * @param Image $image 17 * @param Image $image
30 - * @param ImageTool $imageTool 18 + * @param ImageService $imageService
31 - * @param FileSystem $fileSystem
32 - * @param Cache $cache
33 */ 19 */
34 - public function __construct(Image $image, ImageTool $imageTool, FileSystem $fileSystem, Cache $cache) 20 + public function __construct(Image $image,ImageService $imageService)
35 { 21 {
36 $this->image = $image; 22 $this->image = $image;
37 - $this->imageTool = $imageTool; 23 + $this->imageService = $imageService;
38 - $this->fileSystem = $fileSystem;
39 - $this->cache = $cache;
40 } 24 }
41 25
42 26
...@@ -83,30 +67,7 @@ class ImageRepo ...@@ -83,30 +67,7 @@ class ImageRepo
83 */ 67 */
84 public function saveNew(UploadedFile $uploadFile, $type) 68 public function saveNew(UploadedFile $uploadFile, $type)
85 { 69 {
86 - $storage = $this->getStorage(); 70 + $image = $this->imageService->saveNew($this->image, $uploadFile, $type);
87 - $secureUploads = Setting::get('app-secure-images');
88 - $imageName = str_replace(' ', '-', $uploadFile->getClientOriginalName());
89 -
90 - if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
91 -
92 - $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
93 - while ($storage->exists($imagePath . $imageName)) {
94 - $imageName = str_random(3) . $imageName;
95 - }
96 - $fullPath = $imagePath . $imageName;
97 -
98 - $storage->put($fullPath, file_get_contents($uploadFile->getRealPath()));
99 -
100 - $userId = auth()->user()->id;
101 - $image = $this->image->forceCreate([
102 - 'name' => $imageName,
103 - 'path' => $fullPath,
104 - 'url' => $this->getPublicUrl($fullPath),
105 - 'type' => $type,
106 - 'created_by' => $userId,
107 - 'updated_by' => $userId
108 - ]);
109 -
110 $this->loadThumbs($image); 71 $this->loadThumbs($image);
111 return $image; 72 return $image;
112 } 73 }
...@@ -133,40 +94,10 @@ class ImageRepo ...@@ -133,40 +94,10 @@ class ImageRepo
133 */ 94 */
134 public function destroyImage(Image $image) 95 public function destroyImage(Image $image)
135 { 96 {
136 - $storage = $this->getStorage(); 97 + $this->imageService->destroyImage($image);
137 -
138 - $imageFolder = dirname($image->path);
139 - $imageFileName = basename($image->path);
140 - $allImages = collect($storage->allFiles($imageFolder));
141 -
142 - $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
143 - $expectedIndex = strlen($imagePath) - strlen($imageFileName);
144 - return strpos($imagePath, $imageFileName) === $expectedIndex;
145 - });
146 -
147 - $storage->delete($imagesToDelete->all());
148 -
149 - // Cleanup of empty folders
150 - foreach ($storage->directories($imageFolder) as $directory) {
151 - if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory);
152 - }
153 - if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder);
154 -
155 - $image->delete();
156 return true; 98 return true;
157 } 99 }
158 100
159 - /**
160 - * Check whether or not a folder is empty.
161 - * @param $path
162 - * @return int
163 - */
164 - private function isFolderEmpty($path)
165 - {
166 - $files = $this->getStorage()->files($path);
167 - $folders = $this->getStorage()->directories($path);
168 - return count($files) === 0 && count($folders) === 0;
169 - }
170 101
171 /** 102 /**
172 * Load thumbnails onto an image object. 103 * Load thumbnails onto an image object.
...@@ -193,72 +124,7 @@ class ImageRepo ...@@ -193,72 +124,7 @@ class ImageRepo
193 */ 124 */
194 public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) 125 public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
195 { 126 {
196 - $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/'; 127 + return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
197 - $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
198 -
199 - if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
200 - return $this->getPublicUrl($thumbFilePath);
201 - }
202 -
203 - $storage = $this->getStorage();
204 -
205 - if ($storage->exists($thumbFilePath)) {
206 - return $this->getPublicUrl($thumbFilePath);
207 - }
208 -
209 - // Otherwise create the thumbnail
210 - $thumb = $this->imageTool->make($storage->get($image->path));
211 - if ($keepRatio) {
212 - $thumb->resize($width, null, function ($constraint) {
213 - $constraint->aspectRatio();
214 - $constraint->upsize();
215 - });
216 - } else {
217 - $thumb->fit($width, $height);
218 - }
219 -
220 - $thumbData = (string)$thumb->encode();
221 - $storage->put($thumbFilePath, $thumbData);
222 - $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
223 -
224 - return $this->getPublicUrl($thumbFilePath);
225 - }
226 -
227 - /**
228 - * Gets a public facing url for an image by checking relevant environment variables.
229 - * @param $filePath
230 - * @return string
231 - */
232 - private function getPublicUrl($filePath)
233 - {
234 - if ($this->storageUrl === null) {
235 - $storageUrl = env('STORAGE_URL');
236 -
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'];
241 - }
242 -
243 - $this->storageUrl = $storageUrl;
244 - }
245 -
246 - return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath;
247 - }
248 -
249 -
250 - /**
251 - * Get the storage that will be used for storing images.
252 - * @return FileSystemInstance
253 - */
254 - private function getStorage()
255 - {
256 - if ($this->storageInstance !== null) return $this->storageInstance;
257 -
258 - $storageType = env('STORAGE_TYPE');
259 - $this->storageInstance = $this->fileSystem->disk($storageType);
260 -
261 - return $this->storageInstance;
262 } 128 }
263 129
264 130
......
1 +<?php namespace BookStack\Services\Facades;
2 +
3 +
4 +use Illuminate\Support\Facades\Facade;
5 +
6 +class Images extends Facade
7 +{
8 + /**
9 + * Get the registered name of the component.
10 + *
11 + * @return string
12 + */
13 + protected static function getFacadeAccessor() { return 'images'; }
14 +}
...\ No newline at end of file ...\ No newline at end of file
1 +<?php namespace BookStack\Services;
2 +
3 +use BookStack\Image;
4 +use Intervention\Image\ImageManager;
5 +use Illuminate\Contracts\Filesystem\Factory as FileSystem;
6 +use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
7 +use Illuminate\Contracts\Cache\Repository as Cache;
8 +use Setting;
9 +use Symfony\Component\HttpFoundation\File\UploadedFile;
10 +
11 +class ImageService
12 +{
13 +
14 + protected $imageTool;
15 + protected $fileSystem;
16 + protected $cache;
17 +
18 + /**
19 + * @var FileSystemInstance
20 + */
21 + protected $storageInstance;
22 + protected $storageUrl;
23 +
24 + /**
25 + * ImageService constructor.
26 + * @param $imageTool
27 + * @param $fileSystem
28 + * @param $cache
29 + */
30 + public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
31 + {
32 + $this->imageTool = $imageTool;
33 + $this->fileSystem = $fileSystem;
34 + $this->cache = $cache;
35 + }
36 +
37 + public function saveNew(Image $image, UploadedFile $uploadedFile, $type)
38 + {
39 + $storage = $this->getStorage();
40 + $secureUploads = Setting::get('app-secure-images');
41 + $imageName = str_replace(' ', '-', $uploadedFile->getClientOriginalName());
42 +
43 + if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
44 +
45 + $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
46 + while ($storage->exists($imagePath . $imageName)) {
47 + $imageName = str_random(3) . $imageName;
48 + }
49 + $fullPath = $imagePath . $imageName;
50 +
51 + $storage->put($fullPath, file_get_contents($uploadedFile->getRealPath()));
52 +
53 + $userId = auth()->user()->id;
54 + $image = $image->forceCreate([
55 + 'name' => $imageName,
56 + 'path' => $fullPath,
57 + 'url' => $this->getPublicUrl($fullPath),
58 + 'type' => $type,
59 + 'created_by' => $userId,
60 + 'updated_by' => $userId
61 + ]);
62 +
63 + return $image;
64 + }
65 +
66 + /**
67 + * Get the thumbnail for an image.
68 + * If $keepRatio is true only the width will be used.
69 + * Checks the cache then storage to avoid creating / accessing the filesystem on every check.
70 + *
71 + * @param Image $image
72 + * @param int $width
73 + * @param int $height
74 + * @param bool $keepRatio
75 + * @return string
76 + */
77 + public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
78 + {
79 + $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
80 + $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
81 +
82 + if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
83 + return $this->getPublicUrl($thumbFilePath);
84 + }
85 +
86 + $storage = $this->getStorage();
87 +
88 + if ($storage->exists($thumbFilePath)) {
89 + return $this->getPublicUrl($thumbFilePath);
90 + }
91 +
92 + // Otherwise create the thumbnail
93 + $thumb = $this->imageTool->make($storage->get($image->path));
94 + if ($keepRatio) {
95 + $thumb->resize($width, null, function ($constraint) {
96 + $constraint->aspectRatio();
97 + $constraint->upsize();
98 + });
99 + } else {
100 + $thumb->fit($width, $height);
101 + }
102 +
103 + $thumbData = (string)$thumb->encode();
104 + $storage->put($thumbFilePath, $thumbData);
105 + $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
106 +
107 + return $this->getPublicUrl($thumbFilePath);
108 + }
109 +
110 + /**
111 + * Destroys an Image object along with its files and thumbnails.
112 + * @param Image $image
113 + * @return bool
114 + */
115 + public function destroyImage(Image $image)
116 + {
117 + $storage = $this->getStorage();
118 +
119 + $imageFolder = dirname($image->path);
120 + $imageFileName = basename($image->path);
121 + $allImages = collect($storage->allFiles($imageFolder));
122 +
123 + $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
124 + $expectedIndex = strlen($imagePath) - strlen($imageFileName);
125 + return strpos($imagePath, $imageFileName) === $expectedIndex;
126 + });
127 +
128 + $storage->delete($imagesToDelete->all());
129 +
130 + // Cleanup of empty folders
131 + foreach ($storage->directories($imageFolder) as $directory) {
132 + if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory);
133 + }
134 + if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder);
135 +
136 + $image->delete();
137 + return true;
138 + }
139 +
140 + /**
141 + * Get the storage that will be used for storing images.
142 + * @return FileSystemInstance
143 + */
144 + private function getStorage()
145 + {
146 + if ($this->storageInstance !== null) return $this->storageInstance;
147 +
148 + $storageType = env('STORAGE_TYPE');
149 + $this->storageInstance = $this->fileSystem->disk($storageType);
150 +
151 + return $this->storageInstance;
152 + }
153 +
154 + /**
155 + * Check whether or not a folder is empty.
156 + * @param $path
157 + * @return int
158 + */
159 + private function isFolderEmpty($path)
160 + {
161 + $files = $this->getStorage()->files($path);
162 + $folders = $this->getStorage()->directories($path);
163 + return count($files) === 0 && count($folders) === 0;
164 + }
165 +
166 + /**
167 + * Gets a public facing url for an image by checking relevant environment variables.
168 + * @param $filePath
169 + * @return string
170 + */
171 + private function getPublicUrl($filePath)
172 + {
173 + if ($this->storageUrl === null) {
174 + $storageUrl = env('STORAGE_URL');
175 +
176 + // Get the standard public s3 url if s3 is set as storage type
177 + if ($storageUrl == false && env('STORAGE_TYPE') === 's3') {
178 + $storageDetails = config('filesystems.disks.s3');
179 + $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
180 + }
181 +
182 + $this->storageUrl = $storageUrl;
183 + }
184 +
185 + return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath;
186 + }
187 +
188 +
189 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -217,6 +217,7 @@ return [ ...@@ -217,6 +217,7 @@ return [
217 'Activity' => BookStack\Services\Facades\Activity::class, 217 'Activity' => BookStack\Services\Facades\Activity::class,
218 'Setting' => BookStack\Services\Facades\Setting::class, 218 'Setting' => BookStack\Services\Facades\Setting::class,
219 'Views' => BookStack\Services\Facades\Views::class, 219 'Views' => BookStack\Services\Facades\Views::class,
220 + 'Images' => \BookStack\Services\Facades\Images::class,
220 221
221 ], 222 ],
222 223
......