ajout images
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Admin\Concerns\ScopedToTenant;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\Campaign;
|
||||
use App\Models\Template;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* Images des signatures (logos, bannières), servies publiquement par Nginx sous /img.
|
||||
* Formats acceptés : PNG, JPEG, GIF. Pas de SVG (mal supporté par Outlook et vecteur de script),
|
||||
* pas de WebP (non affiché par plusieurs clients de messagerie).
|
||||
*/
|
||||
class ImageController extends Controller
|
||||
{
|
||||
use ScopedToTenant;
|
||||
|
||||
private const EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif'];
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$dir = $this->dir();
|
||||
$images = collect(File::files($dir))
|
||||
->filter(fn ($f) => in_array(strtolower($f->getExtension()), self::EXTENSIONS, true))
|
||||
->map(function ($f) {
|
||||
$size = @getimagesize($f->getPathname()) ?: [null, null];
|
||||
|
||||
return [
|
||||
'name' => $f->getFilename(),
|
||||
'url' => config('signature.public_url').'/img/'.rawurlencode($f->getFilename()),
|
||||
'kb' => (int) ceil($f->getSize() / 1024),
|
||||
'width' => $size[0],
|
||||
'height' => $size[1],
|
||||
'modified' => $f->getMTime(),
|
||||
'used_in' => $this->usages($f->getFilename()),
|
||||
];
|
||||
})
|
||||
->sortByDesc('modified')->values();
|
||||
|
||||
return view('admin.images.index', ['images' => $images, 'maxKb' => config('signature.image_max_kb')]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'image' => ['required', 'file', 'mimes:png,jpg,jpeg,gif', 'mimetypes:image/png,image/jpeg,image/gif', 'max:'.config('signature.image_max_kb')],
|
||||
'name' => ['nullable', 'string', 'max:80'],
|
||||
], [
|
||||
'image.mimes' => 'Formats acceptés : PNG, JPEG ou GIF.',
|
||||
'image.mimetypes' => 'Le fichier n\'est pas une image PNG, JPEG ou GIF valide.',
|
||||
'image.max' => 'Image trop lourde (2 Mo maximum ; visez moins de 100 Ko pour une signature).',
|
||||
]);
|
||||
|
||||
$file = $request->file('image');
|
||||
abort_unless(@getimagesize($file->getRealPath()), 422, 'Image illisible.');
|
||||
|
||||
$ext = strtolower($file->getClientOriginalExtension()) === 'jpeg' ? 'jpg' : strtolower($file->getClientOriginalExtension());
|
||||
$base = Str::slug(pathinfo($request->input('name') ?: $file->getClientOriginalName(), PATHINFO_FILENAME)) ?: 'image';
|
||||
$name = $this->uniqueName($base, $ext);
|
||||
|
||||
$file->move($this->dir(), $name);
|
||||
@chmod($this->dir().DIRECTORY_SEPARATOR.$name, 0644);
|
||||
AuditLog::record('admin', $this->actor(), 'image.upload', null, ['file' => $name], $this->tenantId());
|
||||
|
||||
return back()->with('status', "Image « {$name} » en ligne. Dans un template : {{site_url}}/img/{$name}");
|
||||
}
|
||||
|
||||
public function destroy(string $name): RedirectResponse
|
||||
{
|
||||
$name = basename($name);
|
||||
$path = $this->dir().DIRECTORY_SEPARATOR.$name;
|
||||
abort_unless(File::isFile($path) && in_array(strtolower(pathinfo($name, PATHINFO_EXTENSION)), self::EXTENSIONS, true), 404);
|
||||
|
||||
if ($used = $this->usages($name)) {
|
||||
return back()->withErrors(['image' => "« {$name} » est utilisée par : ".implode(', ', $used).'. Retirez-la d\'abord.']);
|
||||
}
|
||||
|
||||
File::delete($path);
|
||||
AuditLog::record('admin', $this->actor(), 'image.delete', null, ['file' => $name], $this->tenantId());
|
||||
|
||||
return back()->with('status', "Image « {$name} » supprimée.");
|
||||
}
|
||||
|
||||
private function dir(): string
|
||||
{
|
||||
$dir = config('signature.image_path');
|
||||
File::ensureDirectoryExists($dir);
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
private function uniqueName(string $base, string $ext): string
|
||||
{
|
||||
$name = "{$base}.{$ext}";
|
||||
for ($i = 2; File::exists($this->dir().DIRECTORY_SEPARATOR.$name); $i++) {
|
||||
$name = "{$base}-{$i}.{$ext}";
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/** Templates (publiés ou brouillons) et campagnes qui référencent l'image. */
|
||||
private function usages(string $name): array
|
||||
{
|
||||
$like = '%/img/'.$name.'%';
|
||||
$templates = Template::where('tenant_id', $this->tenantId())
|
||||
->where(fn ($q) => $q->where('html_full', 'like', $like)->orWhere('html_short', 'like', $like)
|
||||
->orWhere('draft_html_full', 'like', $like)->orWhere('draft_html_short', 'like', $like))
|
||||
->pluck('name')->map(fn ($n) => "template {$n}");
|
||||
$campaigns = Campaign::where('tenant_id', $this->tenantId())->where('image_path', $name)
|
||||
->pluck('name')->map(fn ($n) => "campagne {$n}");
|
||||
|
||||
return $templates->merge($campaigns)->all();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user