first commit
This commit is contained in:
commit
0ddc6e835a
|
@ -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"
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'route' => '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')
|
||||||
|
]
|
||||||
|
];
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateImagesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('images', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateAssociateImages extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('associate_images', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Meoran\Images\Http\Controllers;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Laravel\Lumen\Routing\Controller as BaseController;
|
||||||
|
use Meoran\Images\Model\Image;
|
||||||
|
|
||||||
|
class ImagesController extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function get($template, $filename)
|
||||||
|
{
|
||||||
|
switch (strtolower($template)) {
|
||||||
|
case 'original':
|
||||||
|
return $this->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)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Meoran\Images\Model;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Spatie\ImageOptimizer\OptimizerChain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Image
|
||||||
|
* @property Image $content
|
||||||
|
* @package App\Model
|
||||||
|
*/
|
||||||
|
class Image extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $_content;
|
||||||
|
|
||||||
|
public $fillable = [
|
||||||
|
'content',
|
||||||
|
'position',
|
||||||
|
'created',
|
||||||
|
'updated'
|
||||||
|
];
|
||||||
|
protected $dates = ['created_at', 'updated_at'];
|
||||||
|
protected $appends = ['url'];
|
||||||
|
protected $hidden = [];
|
||||||
|
|
||||||
|
public function getUrlAttribute()
|
||||||
|
{
|
||||||
|
return app('url')->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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Meoran\Images\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Intervention\Image\ImageServiceProvider;
|
||||||
|
use Spatie\LaravelImageOptimizer\ImageOptimizerServiceProvider;
|
||||||
|
|
||||||
|
class ImagesServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
require_once __DIR__ . '/../functions.php';
|
||||||
|
|
||||||
|
$this->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'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Meoran\Images\Templates;
|
||||||
|
|
||||||
|
use Intervention\Image\Constraint;
|
||||||
|
use Intervention\Image\Filters\FilterInterface;
|
||||||
|
use Intervention\Image\Image;
|
||||||
|
|
||||||
|
class Large implements FilterInterface
|
||||||
|
{
|
||||||
|
public function applyFilter(Image $image)
|
||||||
|
{
|
||||||
|
return $image->resize(1920, null, function (Constraint $constraint) {
|
||||||
|
$constraint->upsize();
|
||||||
|
$constraint->aspectRatio();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Meoran\Images\Templates;
|
||||||
|
|
||||||
|
use Intervention\Image\Constraint;
|
||||||
|
use Intervention\Image\Filters\FilterInterface;
|
||||||
|
use Intervention\Image\Image;
|
||||||
|
|
||||||
|
class Medium implements FilterInterface
|
||||||
|
{
|
||||||
|
public function applyFilter(Image $image)
|
||||||
|
{
|
||||||
|
return $image->resize(240, null, function (Constraint $constraint) {
|
||||||
|
$constraint->upsize();
|
||||||
|
$constraint->aspectRatio();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Meoran\Images\Templates;
|
||||||
|
|
||||||
|
use Intervention\Image\Constraint;
|
||||||
|
use Intervention\Image\Filters\FilterInterface;
|
||||||
|
use Intervention\Image\Image;
|
||||||
|
|
||||||
|
class Small implements FilterInterface
|
||||||
|
{
|
||||||
|
public function applyFilter(Image $image)
|
||||||
|
{
|
||||||
|
return $image->resize(120, null, function (Constraint $constraint) {
|
||||||
|
$constraint->upsize();
|
||||||
|
$constraint->aspectRatio();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (!function_exists('config_path')) {
|
||||||
|
/**
|
||||||
|
* Get the configuration path.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function config_path($path = '')
|
||||||
|
{
|
||||||
|
return app()->basePath() . '/config' . ($path ? '/' . $path : $path);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue