Modifs
This commit is contained in:
commit
95885c790e
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"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",
|
||||
"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,8 @@
|
|||
<?php
|
||||
|
||||
namespace Meoran\Images\Exception;
|
||||
|
||||
class InvalidContent extends \Exception
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace Meoran\Images\Http\Controllers;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
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(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'image' => 'required|file|image'
|
||||
]);
|
||||
|
||||
$image = new Image(['content' => $request->input('image')]);
|
||||
$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,174 @@
|
|||
<?php
|
||||
|
||||
namespace Meoran\Images\Model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use Meoran\Images\Exception\InvalidContent;
|
||||
use Spatie\ImageOptimizer\OptimizerChain;
|
||||
use \Intervention\Image\Image as InterventionImage;
|
||||
|
||||
/**
|
||||
* Class Image
|
||||
* @property Image $content
|
||||
* @package App\Model
|
||||
*/
|
||||
class Image extends Model
|
||||
{
|
||||
|
||||
protected $_content;
|
||||
|
||||
protected $table = 'images';
|
||||
|
||||
public $fillable = [
|
||||
'filename',
|
||||
'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 InvalidContent("Content must be defined to save image");
|
||||
}
|
||||
if (!($model->content instanceof InterventionImage)) {
|
||||
throw new InvalidContent("Content must be an instance of Intervention\Image");
|
||||
}
|
||||
$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(mb_strtolower(str_slug($filename, '')), 2), 0, 2);
|
||||
|
||||
$path = $basePath . '/' . implode('/', $parts) . '/' . $filename;
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
static function generateRandomFilename()
|
||||
{
|
||||
return mb_strtolower(str_random(60));
|
||||
}
|
||||
|
||||
static function sanitizeFilename($filename)
|
||||
{
|
||||
return str_slug($filename, '-');
|
||||
}
|
||||
|
||||
public function setFilenameAttribute($value)
|
||||
{
|
||||
$pattern = '/[^a-z_\-\.0-9]/i';
|
||||
if (preg_match($pattern, $value)) {
|
||||
throw new \InvalidArgumentException("Invalid filename. Must be only composed only with a-z, A-Z, 0-9 and dot minus underscore");
|
||||
}
|
||||
$this->attributes['filename'] = $value;
|
||||
}
|
||||
|
||||
public function fileExist() {
|
||||
return is_file($this->getPath());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (empty($this->_content)) {
|
||||
try {
|
||||
$this->_content = app('image')->make($this->getPath());
|
||||
} catch (NotReadableException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
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,43 @@
|
|||
<?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->post('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