From 0ddc6e835a8564cfafb7e8b448f2532f58250809 Mon Sep 17 00:00:00 2001 From: Cassandre Cantet Date: Tue, 3 Oct 2017 17:05:46 +0200 Subject: [PATCH] first commit --- composer.json | 28 ++++ config/image.php | 15 ++ ...2017_10_03_135933_create_images__table.php | 34 +++++ ...7_10_03_140902_create_associate_images.php | 38 +++++ src/Http/Controllers/ImagesController.php | 129 ++++++++++++++++ src/Model/Image.php | 140 ++++++++++++++++++ src/Providers/ImagesServiceProvider.php | 44 ++++++ src/Templates/Large.php | 18 +++ src/Templates/Medium.php | 18 +++ src/Templates/Small.php | 18 +++ src/functions.php | 14 ++ 11 files changed, 496 insertions(+) create mode 100755 composer.json create mode 100644 config/image.php create mode 100644 database/migrations/2017_10_03_135933_create_images__table.php create mode 100644 database/migrations/2017_10_03_140902_create_associate_images.php create mode 100755 src/Http/Controllers/ImagesController.php create mode 100755 src/Model/Image.php create mode 100755 src/Providers/ImagesServiceProvider.php create mode 100755 src/Templates/Large.php create mode 100755 src/Templates/Medium.php create mode 100755 src/Templates/Small.php create mode 100755 src/functions.php diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..cf31063 --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "meoran/images", + "description": "Gestion des images en base de données", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Cassandre Cantet", + "email": "pro@cassandrecantet.fr" + } + ], + "require": { + "php": ">=7.0.0", + "laravel/lumen-framework": "5.4.*", + "symfony/process" : "3.*", + "intervention/image": "^2.4", + "intervention/imagecache": "^2.3", + "jenssegers/model": "^1.1", + "spatie/laravel-image-optimizer": "^1.1" + }, + "autoload": { + "psr-4": { + "Meoran\\Images\\": "src/" + } + }, + "prefer-stable": true, + "minimum-stability": "dev" +} diff --git a/config/image.php b/config/image.php new file mode 100644 index 0000000..2ae3e7a --- /dev/null +++ b/config/image.php @@ -0,0 +1,15 @@ + 'images', + 'path' => storage_path('images'), + 'templates' => array( + 'small' => \Meoran\Images\Templates\Small::class, + 'medium' => \Meoran\Images\Templates\Medium::class, + 'large' => \Meoran\Images\Templates\Large::class, + ), + 'lifetime' => 10, + 'cache' => [ + 'path' => storage_path('app') + ] +]; \ No newline at end of file diff --git a/database/migrations/2017_10_03_135933_create_images__table.php b/database/migrations/2017_10_03_135933_create_images__table.php new file mode 100644 index 0000000..c49fdbc --- /dev/null +++ b/database/migrations/2017_10_03_135933_create_images__table.php @@ -0,0 +1,34 @@ +integer('id', true); + $table->string('filename'); + $table->dateTime('created_at'); + $table->dateTime('updated_at'); + $table->unique('filename'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('images'); + } +} diff --git a/database/migrations/2017_10_03_140902_create_associate_images.php b/database/migrations/2017_10_03_140902_create_associate_images.php new file mode 100644 index 0000000..dc342fc --- /dev/null +++ b/database/migrations/2017_10_03_140902_create_associate_images.php @@ -0,0 +1,38 @@ +integer('id', true); + $table->integer('image_id'); + $table->string('relation_type'); + $table->integer('relation_id'); + $table->integer('position')->nullable(); + + $table->foreign('image_id')->references('id')->on('images'); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('associate_images'); + + } +} diff --git a/src/Http/Controllers/ImagesController.php b/src/Http/Controllers/ImagesController.php new file mode 100755 index 0000000..59ade58 --- /dev/null +++ b/src/Http/Controllers/ImagesController.php @@ -0,0 +1,129 @@ +getOriginal($filename); + case 'download': + return $this->getDownload($filename); + default: + return $this->getImage($template, $filename); + } + } + + public function upload() + { + $pathImage = storage_path('Sunset_at_Tiergarten_UHD.jpg'); + + $image = new Image(['content' => $pathImage]); + $image->save(); + + return response()->json($image); + } + + private function getOriginal($filename) + { + $path = $this->getImagePathOrAbort($filename); + return $this->buildResponse(file_get_contents($path)); + } + + private function getDownload($filename) + { + $response = $this->getOriginal($filename); + + return $response->header( + 'Content-Disposition', + 'attachment; filename=' . $filename + ); + } + + private function getImagePathOrAbort($filename) + { + $path = Image::getAbsolutePath($filename); + if (is_file($path)) { + return $path; + } + abort(404); + } + + public function getImage($template, $filename) + { + $template = $this->getTemplate($template); + $path = $this->getImagePathOrAbort($filename); + + $lifetime = config('image.lifetime'); + if (empty($lifetime)) { + $content = $this->applyTemplate($template,$path)->encode(); + } else { + $content = app('image')->cache(function ($image) use ($template, $path) { + $this->applyTemplate($template,$path,$image); + return $image; + }, $lifetime); + } + + return $this->buildResponse($content); + } + + private function applyTemplate($template, $path, $image = null) + { + if (empty($image)) { + $image = app('image'); + } + + if ($template instanceof Closure) { + // build from closure callback template + return $template($image->make($path)); + } else { + // build from filter template + return $image->make($path)->filter($template); + } + } + + /** + * Returns corresponding template object from given template name + * + * @param string $template + * @return mixed + */ + private function getTemplate($template) + { + $template = config("image.templates.{$template}"); + switch (true) { + // closure template found + case is_callable($template): + return $template; + + // filter template found + case class_exists($template): + return new $template; + + default: + // template not found + abort(404); + break; + } + } + + private function buildResponse($content) + { + // define mime type + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content); + + // return http response + return response($content, 200, array( + 'Content-Type' => $mime, + 'Cache-Control' => 'max-age=' . (config('image.lifetime') * 60) . ', public', + 'Etag' => md5($content) + )); + } +} diff --git a/src/Model/Image.php b/src/Model/Image.php new file mode 100755 index 0000000..a47bf62 --- /dev/null +++ b/src/Model/Image.php @@ -0,0 +1,140 @@ +to('/'.config('image.route').'/original/' . $this->filename); + } + + protected static function boot() + { + parent::boot(); + + static::creating(function(Image $model) { + if (empty($model->content)) { + throw new \Exception("Content of picture can't be empty at creation"); + } + $model->savePicture(); + }); + + static::updating(function (Image $model) { + $model->savePicture(); + }); + + static::deleted(function (Image $model) { + $model->deletePicture(); + }); + + } + + public function getPath() + { + if (empty($this->filename)) { + return null; + } + + return self::getAbsolutePath($this->filename); + } + + static function getAbsolutePath($filename) + { + $basePath = config('image.path'); + + $parts = array_slice(str_split($filename, 2), 0, 2); + + $path = $basePath.'/'.implode('/', $parts).'/'.$filename; + + return $path; + } + + static function generateRandomFilename() + { + return mb_strtolower(str_random(60)); + } + + public function generateFilename($force = false) + { + if ($this->filename && !$force) { + return; + } + $this->filename = self::generateRandomFilename(); + } + + public function setContentAttribute($content) + { + $this->_content = app('image')->make($content); + } + + public function getContentAttribute($value) + { + return $this->_content; + } + + protected function savePicture() + { + if (empty($this->content)) { + return true; + } + $this->generateFilename(); + return $this->saveContent(); + } + + protected function saveContent() + { + if (empty($this->content)) { + throw new \InvalidArgumentException("Content is Empty"); + } + $path = $this->getPath(); + $dir = dirname($path); + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + $res = $this->content->save($path); + app(OptimizerChain::class)->optimize($path); + return $res; + } + + protected function deletePicture() + { + $path = $this->getPath(); + if (is_file($path)) { + return unlink($path); + } + return true; + } + + public function toArray() + { + $attributes = parent::toArray(); + + if (isset($attributes['pivot']) && array_key_exists('position', $attributes['pivot'])) { + $attributes['position'] = $attributes['pivot']['position']; + } + unset($attributes['pivot']); + return $attributes; + } +} diff --git a/src/Providers/ImagesServiceProvider.php b/src/Providers/ImagesServiceProvider.php new file mode 100755 index 0000000..5eec64e --- /dev/null +++ b/src/Providers/ImagesServiceProvider.php @@ -0,0 +1,44 @@ +loadMigrationsFrom(__DIR__ . '/../../database/migrations'); + + $this->mergeConfigFrom(__DIR__ . '/../../config/image.php', 'image'); + + + $this->app->register(ImageServiceProvider::class); + $this->app->register(ImageOptimizerServiceProvider::class); + + $this->loadRoutes(); + + if ($this->app->runningInConsole()) { + $this->publishes([ +// __DIR__ . '/../../config/synchronize.php' => base_path('config/synchronize.php') + ]); + } + } + + public function loadRoutes() + { + $this->app->get('images/upload', [ + 'as' => 'uploadImage', 'uses' => '\Meoran\Images\Http\Controllers\ImagesController@upload' + ]); + + $this->app->get('images/{template}/{filename}', [ + 'as' => 'getPicture', 'uses' => '\Meoran\Images\Http\Controllers\ImagesController@get' + ]); + } + +} diff --git a/src/Templates/Large.php b/src/Templates/Large.php new file mode 100755 index 0000000..27baadd --- /dev/null +++ b/src/Templates/Large.php @@ -0,0 +1,18 @@ +resize(1920, null, function (Constraint $constraint) { + $constraint->upsize(); + $constraint->aspectRatio(); + }); + } +} \ No newline at end of file diff --git a/src/Templates/Medium.php b/src/Templates/Medium.php new file mode 100755 index 0000000..e315c9c --- /dev/null +++ b/src/Templates/Medium.php @@ -0,0 +1,18 @@ +resize(240, null, function (Constraint $constraint) { + $constraint->upsize(); + $constraint->aspectRatio(); + }); + } +} \ No newline at end of file diff --git a/src/Templates/Small.php b/src/Templates/Small.php new file mode 100755 index 0000000..43e6875 --- /dev/null +++ b/src/Templates/Small.php @@ -0,0 +1,18 @@ +resize(120, null, function (Constraint $constraint) { + $constraint->upsize(); + $constraint->aspectRatio(); + }); + } +} \ No newline at end of file diff --git a/src/functions.php b/src/functions.php new file mode 100755 index 0000000..6953925 --- /dev/null +++ b/src/functions.php @@ -0,0 +1,14 @@ +basePath() . '/config' . ($path ? '/' . $path : $path); + } +}