Upload beautify

This commit is contained in:
2026-02-14 15:14:12 +01:00
parent e129618910
commit 79192345e3
249 changed files with 24436 additions and 1021 deletions

View File

@@ -0,0 +1,169 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\DB;
use Intervention\Image\ImageManagerStatic as Image;
use RuntimeException;
use Carbon\Carbon;
use Illuminate\Http\UploadedFile;
class AvatarService
{
protected $sizes = [
'xs' => 32,
'sm' => 64,
'md' => 128,
'lg' => 256,
'xl' => 512,
];
protected $quality = 85;
public function __construct()
{
// Guard: if Intervention Image is not installed, defer error until actual use
if (class_exists(\Intervention\Image\ImageManagerStatic::class)) {
try {
Image::configure(['driver' => extension_loaded('gd') ? 'gd' : 'imagick']);
$this->imageAvailable = true;
} catch (\Throwable $e) {
// If configuration fails, treat as unavailable and log for diagnostics
logger()->warning('Intervention Image present but configuration failed: '.$e->getMessage());
$this->imageAvailable = false;
}
} else {
$this->imageAvailable = false;
}
}
/**
* Process an uploaded file for a user and store webp sizes.
* Returns the computed sha1 hash.
*
* @param int $userId
* @param UploadedFile $file
* @return string sha1 hash
*/
public function storeFromUploadedFile(int $userId, UploadedFile $file): string
{
if (! $this->imageAvailable) {
throw new RuntimeException('Intervention Image is not available. If you just installed the package, restart your PHP process (php artisan serve or PHP-FPM) and run `composer dump-autoload -o`.');
}
// Load image and re-encode to webp after validating
try {
$img = Image::make($file->getRealPath());
} catch (\Throwable $e) {
throw new RuntimeException('Failed to read uploaded image: '.$e->getMessage());
}
// Ensure square center crop per spec
$max = max($img->width(), $img->height());
$img->fit($max, $max);
$basePath = "avatars/{$userId}";
Storage::disk('public')->makeDirectory($basePath);
// Save original as webp
$originalData = (string) $img->encode('webp', $this->quality);
Storage::disk('public')->put($basePath . '/original.webp', $originalData);
// Generate sizes
foreach ($this->sizes as $name => $size) {
$resized = $img->resize($size, $size, function ($constraint) {
$constraint->upsize();
})->encode('webp', $this->quality);
Storage::disk('public')->put("{$basePath}/{$size}.webp", (string)$resized);
}
$hash = sha1($originalData);
$mime = 'image/webp';
// Persist metadata to user_profiles if exists, otherwise users table fallbacks
if (SchemaHasTable('user_profiles')) {
DB::table('user_profiles')->where('user_id', $userId)->update([
'avatar_hash' => $hash,
'avatar_updated_at' => Carbon::now(),
'avatar_mime' => $mime,
]);
} else {
DB::table('users')->where('id', $userId)->update([
'avatar_hash' => $hash,
'avatar_updated_at' => Carbon::now(),
'avatar_mime' => $mime,
]);
}
return $hash;
}
/**
* Process a legacy file path for a user (path-to-file).
* Returns sha1 or null when missing.
*
* @param int $userId
* @param string $path Absolute filesystem path
* @return string|null
*/
public function storeFromLegacyFile(int $userId, string $path): ?string
{
if (!file_exists($path) || !is_readable($path)) {
return null;
}
try {
$img = Image::make($path);
} catch (\Exception $e) {
return null;
}
$max = max($img->width(), $img->height());
$img->fit($max, $max);
$basePath = "avatars/{$userId}";
Storage::disk('public')->makeDirectory($basePath);
$originalData = (string) $img->encode('webp', $this->quality);
Storage::disk('public')->put($basePath . '/original.webp', $originalData);
foreach ($this->sizes as $name => $size) {
$resized = $img->resize($size, $size, function ($constraint) {
$constraint->upsize();
})->encode('webp', $this->quality);
Storage::disk('public')->put("{$basePath}/{$size}.webp", (string)$resized);
}
$hash = sha1($originalData);
$mime = 'image/webp';
if (SchemaHasTable('user_profiles')) {
DB::table('user_profiles')->where('user_id', $userId)->update([
'avatar_hash' => $hash,
'avatar_updated_at' => Carbon::now(),
'avatar_mime' => $mime,
]);
} else {
DB::table('users')->where('id', $userId)->update([
'avatar_hash' => $hash,
'avatar_updated_at' => Carbon::now(),
'avatar_mime' => $mime,
]);
}
return $hash;
}
}
/**
* Helper: check for table existence without importing Schema facade repeatedly
*/
function SchemaHasTable(string $name): bool
{
try {
return \Illuminate\Support\Facades\Schema::hasTable($name);
} catch (\Throwable $e) {
return false;
}
}