]> BookStack Code Mirror - bookstack/commitdiff
Added an image service and facade, Cleaned Image Model
authorDan Brown <redacted>
Wed, 9 Dec 2015 19:50:17 +0000 (19:50 +0000)
committerDan Brown <redacted>
Wed, 9 Dec 2015 19:50:17 +0000 (19:50 +0000)
app/Entity.php
app/Image.php
app/Ownable.php [new file with mode: 0644]
app/Providers/CustomFacadeProvider.php
app/Repos/ImageRepo.php
app/Services/Facades/Images.php [new file with mode: 0644]
app/Services/ImageService.php [new file with mode: 0644]
config/app.php

index 26878042e5bbca8c51edad3d18236ac00eb6b953..5ccc016a330a897b45742b1e42920b57533a9bfe 100644 (file)
@@ -7,23 +7,7 @@ use Illuminate\Database\Eloquent\Model;
 abstract class Entity extends Model
 {
 
-    /**
-     * Relation for the user that created this entity.
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
-     */
-    public function createdBy()
-    {
-        return $this->belongsTo('BookStack\User', 'created_by');
-    }
-
-    /**
-     * Relation for the user that updated this entity.
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
-     */
-    public function updatedBy()
-    {
-        return $this->belongsTo('BookStack\User', 'updated_by');
-    }
+    use Ownable;
 
     /**
      * Compares this entity to another given entity.
index 7c77440f9919313674418e1cfc0df303c72f04ce..66d54ba302befb6c9df5f232be5a262dcce4a08f 100644 (file)
@@ -3,22 +3,22 @@
 namespace BookStack;
 
 
-class Image extends Entity
+use Images;
+
+class Image
 {
+    use Ownable;
 
     protected $fillable = ['name'];
 
-    public function getFilePath()
-    {
-        return storage_path() . $this->url;
-    }
-
     /**
-     * Get the url for this item.
-     * @return string
+     * Get a thumbnail for this image.
+     * @param  int       $width
+     * @param  int       $height
+     * @param bool|false $hardCrop
      */
-    public function getUrl()
+    public function getThumb($width, $height, $hardCrop = false)
     {
-        return public_path() . $this->url;
+        Images::getThumbnail($this, $width, $height, $hardCrop);
     }
 }
diff --git a/app/Ownable.php b/app/Ownable.php
new file mode 100644 (file)
index 0000000..d6505b7
--- /dev/null
@@ -0,0 +1,23 @@
+<?php namespace BookStack;
+
+
+trait Ownable
+{
+    /**
+     * Relation for the user that created this entity.
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     */
+    public function createdBy()
+    {
+        return $this->belongsTo('BookStack\User', 'created_by');
+    }
+
+    /**
+     * Relation for the user that updated this entity.
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     */
+    public function updatedBy()
+    {
+        return $this->belongsTo('BookStack\User', 'updated_by');
+    }
+}
\ No newline at end of file
index bd4b2b515d47dbff9cad199fcc62b361caffb3b2..1df14a076f9e667d58c1ef079626f947ee5ce7f9 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace BookStack\Providers;
 
+use BookStack\Services\ImageService;
 use BookStack\Services\ViewService;
 use Illuminate\Support\ServiceProvider;
 use BookStack\Services\ActivityService;
@@ -40,5 +41,12 @@ class CustomFacadeProvider extends ServiceProvider
                 $this->app->make('Illuminate\Contracts\Cache\Repository')
             );
         });
+        $this->app->bind('images', function() {
+            return new ImageService(
+                $this->app->make('Intervention\Image\ImageManager'),
+                $this->app->make('Illuminate\Contracts\Filesystem\Factory'),
+                $this->app->make('Illuminate\Contracts\Cache\Repository')
+            );
+        });
     }
 }
index 0da243f7c8e2d9a4b3eb18aba3b931276302f06e..56b0ba98d141be86ed80a25599b35ba18ac23366 100644 (file)
@@ -2,10 +2,7 @@
 
 
 use BookStack\Image;
-use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
-use Intervention\Image\ImageManager as ImageTool;
-use Illuminate\Contracts\Filesystem\Factory as FileSystem;
-use Illuminate\Contracts\Cache\Repository as Cache;
+use BookStack\Services\ImageService;
 use Setting;
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 
@@ -13,30 +10,17 @@ class ImageRepo
 {
 
     protected $image;
-    protected $imageTool;
-    protected $fileSystem;
-    protected $cache;
-
-    /**
-     * @var FileSystemInstance
-     */
-    protected $storageInstance;
-    protected $storageUrl;
-
+    protected $imageService;
 
     /**
      * ImageRepo constructor.
-     * @param Image      $image
-     * @param ImageTool  $imageTool
-     * @param FileSystem $fileSystem
-     * @param Cache      $cache
+     * @param Image        $image
+     * @param ImageService $imageService
      */
-    public function __construct(Image $image, ImageTool $imageTool, FileSystem $fileSystem, Cache $cache)
+    public function __construct(Image $image,ImageService $imageService)
     {
         $this->image = $image;
-        $this->imageTool = $imageTool;
-        $this->fileSystem = $fileSystem;
-        $this->cache = $cache;
+        $this->imageService = $imageService;
     }
 
 
@@ -83,30 +67,7 @@ class ImageRepo
      */
     public function saveNew(UploadedFile $uploadFile, $type)
     {
-        $storage = $this->getStorage();
-        $secureUploads = Setting::get('app-secure-images');
-        $imageName = str_replace(' ', '-', $uploadFile->getClientOriginalName());
-
-        if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
-
-        $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
-        while ($storage->exists($imagePath . $imageName)) {
-            $imageName = str_random(3) . $imageName;
-        }
-        $fullPath = $imagePath . $imageName;
-
-        $storage->put($fullPath, file_get_contents($uploadFile->getRealPath()));
-
-        $userId = auth()->user()->id;
-        $image = $this->image->forceCreate([
-            'name' => $imageName,
-            'path' => $fullPath,
-            'url' => $this->getPublicUrl($fullPath),
-            'type' => $type,
-            'created_by' => $userId,
-            'updated_by' => $userId
-        ]);
-
+        $image = $this->imageService->saveNew($this->image, $uploadFile, $type);
         $this->loadThumbs($image);
         return $image;
     }
@@ -133,40 +94,10 @@ class ImageRepo
      */
     public function destroyImage(Image $image)
     {
-        $storage = $this->getStorage();
-
-        $imageFolder = dirname($image->path);
-        $imageFileName = basename($image->path);
-        $allImages = collect($storage->allFiles($imageFolder));
-
-        $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
-            $expectedIndex = strlen($imagePath) - strlen($imageFileName);
-            return strpos($imagePath, $imageFileName) === $expectedIndex;
-        });
-
-        $storage->delete($imagesToDelete->all());
-
-        // Cleanup of empty folders
-        foreach ($storage->directories($imageFolder) as $directory) {
-            if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory);
-        }
-        if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder);
-
-        $image->delete();
+        $this->imageService->destroyImage($image);
         return true;
     }
 
-    /**
-     * Check whether or not a folder is empty.
-     * @param $path
-     * @return int
-     */
-    private function isFolderEmpty($path)
-    {
-        $files = $this->getStorage()->files($path);
-        $folders = $this->getStorage()->directories($path);
-        return count($files) === 0 && count($folders) === 0;
-    }
 
     /**
      * Load thumbnails onto an image object.
@@ -193,72 +124,7 @@ class ImageRepo
      */
     public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
     {
-        $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
-        $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
-
-        if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
-            return $this->getPublicUrl($thumbFilePath);
-        }
-
-        $storage = $this->getStorage();
-
-        if ($storage->exists($thumbFilePath)) {
-            return $this->getPublicUrl($thumbFilePath);
-        }
-
-        // Otherwise create the thumbnail
-        $thumb = $this->imageTool->make($storage->get($image->path));
-        if ($keepRatio) {
-            $thumb->resize($width, null, function ($constraint) {
-                $constraint->aspectRatio();
-                $constraint->upsize();
-            });
-        } else {
-            $thumb->fit($width, $height);
-        }
-
-        $thumbData = (string)$thumb->encode();
-        $storage->put($thumbFilePath, $thumbData);
-        $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
-
-        return $this->getPublicUrl($thumbFilePath);
-    }
-
-    /**
-     * Gets a public facing url for an image by checking relevant environment variables.
-     * @param $filePath
-     * @return string
-     */
-    private function getPublicUrl($filePath)
-    {
-        if ($this->storageUrl === null) {
-            $storageUrl = env('STORAGE_URL');
-
-            // Get the standard public s3 url if s3 is set as storage type
-            if ($storageUrl == false && env('STORAGE_TYPE') === 's3') {
-                $storageDetails = config('filesystems.disks.s3');
-                $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
-            }
-
-            $this->storageUrl = $storageUrl;
-        }
-
-        return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath;
-    }
-
-
-    /**
-     * Get the storage that will be used for storing images.
-     * @return FileSystemInstance
-     */
-    private function getStorage()
-    {
-        if ($this->storageInstance !== null) return $this->storageInstance;
-
-        $storageType = env('STORAGE_TYPE');
-        $this->storageInstance = $this->fileSystem->disk($storageType);
-
-        return $this->storageInstance;
+        return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
     }
 
 
diff --git a/app/Services/Facades/Images.php b/app/Services/Facades/Images.php
new file mode 100644 (file)
index 0000000..219f069
--- /dev/null
@@ -0,0 +1,14 @@
+<?php namespace BookStack\Services\Facades;
+
+
+use Illuminate\Support\Facades\Facade;
+
+class Images extends Facade
+{
+    /**
+     * Get the registered name of the component.
+     *
+     * @return string
+     */
+    protected static function getFacadeAccessor() { return 'images'; }
+}
\ No newline at end of file
diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php
new file mode 100644 (file)
index 0000000..e6ee4cf
--- /dev/null
@@ -0,0 +1,189 @@
+<?php namespace BookStack\Services;
+
+use BookStack\Image;
+use Intervention\Image\ImageManager;
+use Illuminate\Contracts\Filesystem\Factory as FileSystem;
+use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
+use Illuminate\Contracts\Cache\Repository as Cache;
+use Setting;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class ImageService
+{
+
+    protected $imageTool;
+    protected $fileSystem;
+    protected $cache;
+
+    /**
+     * @var FileSystemInstance
+     */
+    protected $storageInstance;
+    protected $storageUrl;
+
+    /**
+     * ImageService constructor.
+     * @param $imageTool
+     * @param $fileSystem
+     * @param $cache
+     */
+    public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
+    {
+        $this->imageTool = $imageTool;
+        $this->fileSystem = $fileSystem;
+        $this->cache = $cache;
+    }
+
+    public function saveNew(Image $image, UploadedFile $uploadedFile, $type)
+    {
+        $storage = $this->getStorage();
+        $secureUploads = Setting::get('app-secure-images');
+        $imageName = str_replace(' ', '-', $uploadedFile->getClientOriginalName());
+
+        if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
+
+        $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
+        while ($storage->exists($imagePath . $imageName)) {
+            $imageName = str_random(3) . $imageName;
+        }
+        $fullPath = $imagePath . $imageName;
+
+        $storage->put($fullPath, file_get_contents($uploadedFile->getRealPath()));
+
+        $userId = auth()->user()->id;
+        $image = $image->forceCreate([
+            'name' => $imageName,
+            'path' => $fullPath,
+            'url' => $this->getPublicUrl($fullPath),
+            'type' => $type,
+            'created_by' => $userId,
+            'updated_by' => $userId
+        ]);
+
+        return $image;
+    }
+
+    /**
+     * Get the thumbnail for an image.
+     * If $keepRatio is true only the width will be used.
+     * Checks the cache then storage to avoid creating / accessing the filesystem on every check.
+     *
+     * @param Image $image
+     * @param int   $width
+     * @param int   $height
+     * @param bool  $keepRatio
+     * @return string
+     */
+    public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
+    {
+        $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
+        $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
+
+        if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
+            return $this->getPublicUrl($thumbFilePath);
+        }
+
+        $storage = $this->getStorage();
+
+        if ($storage->exists($thumbFilePath)) {
+            return $this->getPublicUrl($thumbFilePath);
+        }
+
+        // Otherwise create the thumbnail
+        $thumb = $this->imageTool->make($storage->get($image->path));
+        if ($keepRatio) {
+            $thumb->resize($width, null, function ($constraint) {
+                $constraint->aspectRatio();
+                $constraint->upsize();
+            });
+        } else {
+            $thumb->fit($width, $height);
+        }
+
+        $thumbData = (string)$thumb->encode();
+        $storage->put($thumbFilePath, $thumbData);
+        $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
+
+        return $this->getPublicUrl($thumbFilePath);
+    }
+
+    /**
+     * Destroys an Image object along with its files and thumbnails.
+     * @param Image $image
+     * @return bool
+     */
+    public function destroyImage(Image $image)
+    {
+        $storage = $this->getStorage();
+
+        $imageFolder = dirname($image->path);
+        $imageFileName = basename($image->path);
+        $allImages = collect($storage->allFiles($imageFolder));
+
+        $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
+            $expectedIndex = strlen($imagePath) - strlen($imageFileName);
+            return strpos($imagePath, $imageFileName) === $expectedIndex;
+        });
+
+        $storage->delete($imagesToDelete->all());
+
+        // Cleanup of empty folders
+        foreach ($storage->directories($imageFolder) as $directory) {
+            if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory);
+        }
+        if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder);
+
+        $image->delete();
+        return true;
+    }
+
+    /**
+     * Get the storage that will be used for storing images.
+     * @return FileSystemInstance
+     */
+    private function getStorage()
+    {
+        if ($this->storageInstance !== null) return $this->storageInstance;
+
+        $storageType = env('STORAGE_TYPE');
+        $this->storageInstance = $this->fileSystem->disk($storageType);
+
+        return $this->storageInstance;
+    }
+
+    /**
+     * Check whether or not a folder is empty.
+     * @param $path
+     * @return int
+     */
+    private function isFolderEmpty($path)
+    {
+        $files = $this->getStorage()->files($path);
+        $folders = $this->getStorage()->directories($path);
+        return count($files) === 0 && count($folders) === 0;
+    }
+
+    /**
+     * Gets a public facing url for an image by checking relevant environment variables.
+     * @param $filePath
+     * @return string
+     */
+    private function getPublicUrl($filePath)
+    {
+        if ($this->storageUrl === null) {
+            $storageUrl = env('STORAGE_URL');
+
+            // Get the standard public s3 url if s3 is set as storage type
+            if ($storageUrl == false && env('STORAGE_TYPE') === 's3') {
+                $storageDetails = config('filesystems.disks.s3');
+                $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
+            }
+
+            $this->storageUrl = $storageUrl;
+        }
+
+        return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath;
+    }
+
+
+}
\ No newline at end of file
index aa7c0b561fccb65e309f5e882aa9f05dd77f12f4..b9c20763205b2007e9860e6c7ed966eaa366cbb0 100644 (file)
@@ -13,7 +13,7 @@ return [
     |
     */
 
-    'debug'           => env('APP_DEBUG', false),
+    'debug' => env('APP_DEBUG', false),
 
     /*
     |--------------------------------------------------------------------------
@@ -26,7 +26,7 @@ return [
     |
     */
 
-    'url'             => env('APP_URL', 'http://localhost'),
+    'url' => env('APP_URL', 'http://localhost'),
 
     /*
     |--------------------------------------------------------------------------
@@ -39,7 +39,7 @@ return [
     |
     */
 
-    'timezone'        => 'UTC',
+    'timezone' => 'UTC',
 
     /*
     |--------------------------------------------------------------------------
@@ -52,7 +52,7 @@ return [
     |
     */
 
-    'locale'          => 'en',
+    'locale' => 'en',
 
     /*
     |--------------------------------------------------------------------------
@@ -78,9 +78,9 @@ return [
     |
     */
 
-    'key'             => env('APP_KEY', 'AbAZchsay4uBTU33RubBzLKw203yqSqr'),
+    'key' => env('APP_KEY', 'AbAZchsay4uBTU33RubBzLKw203yqSqr'),
 
-    'cipher'          => 'AES-256-CBC',
+    'cipher' => 'AES-256-CBC',
 
     /*
     |--------------------------------------------------------------------------
@@ -95,7 +95,7 @@ return [
     |
     */
 
-    'log'             => 'single',
+    'log' => 'single',
 
     /*
     |--------------------------------------------------------------------------
@@ -108,7 +108,7 @@ return [
     |
     */
 
-    'providers'       => [
+    'providers' => [
 
         /*
          * Laravel Framework Service Providers...
@@ -167,7 +167,7 @@ return [
     |
     */
 
-    'aliases'         => [
+    'aliases' => [
 
         'App'       => Illuminate\Support\Facades\App::class,
         'Artisan'   => Illuminate\Support\Facades\Artisan::class,
@@ -208,15 +208,16 @@ return [
          */
 
         'ImageTool' => Intervention\Image\Facades\Image::class,
-        'Debugbar' => Barryvdh\Debugbar\Facade::class,
+        'Debugbar'  => Barryvdh\Debugbar\Facade::class,
 
         /**
          * Custom
          */
 
-        'Activity'  => BookStack\Services\Facades\Activity::class,
-        'Setting'   => BookStack\Services\Facades\Setting::class,
-        'Views' => BookStack\Services\Facades\Views::class,
+        'Activity' => BookStack\Services\Facades\Activity::class,
+        'Setting'  => BookStack\Services\Facades\Setting::class,
+        'Views'    => BookStack\Services\Facades\Views::class,
+        'Images'   => \BookStack\Services\Facades\Images::class,
 
     ],