more fixes

This commit is contained in:
2026-03-12 07:22:38 +01:00
parent 547215cbe8
commit 4f576ceb04
226 changed files with 14380 additions and 4453 deletions

View File

@@ -18,6 +18,18 @@ class AvatarService
private const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
private const ALLOWED_POSITIONS = [
'top-left',
'top',
'top-right',
'left',
'center',
'right',
'bottom-left',
'bottom',
'bottom-right',
];
protected $sizes = [
'xs' => 32,
'sm' => 64,
@@ -50,13 +62,13 @@ class AvatarService
}
}
public function storeFromUploadedFile(int $userId, UploadedFile $file): string
public function storeFromUploadedFile(int $userId, UploadedFile $file, string $position = 'center'): string
{
$this->assertImageManagerAvailable();
$this->assertStorageIsAllowed();
$binary = $this->assertSecureImageUpload($file);
return $this->storeFromBinary($userId, $binary);
return $this->storeFromBinary($userId, $binary, $position);
}
public function storeFromLegacyFile(int $userId, string $path): ?string
@@ -76,10 +88,26 @@ class AvatarService
return $this->storeFromBinary($userId, $binary);
}
private function storeFromBinary(int $userId, string $binary): string
public function removeAvatar(int $userId): void
{
$diskName = (string) config('avatars.disk', 's3');
Storage::disk($diskName)->deleteDirectory("avatars/{$userId}");
UserProfile::query()->updateOrCreate(
['user_id' => $userId],
[
'avatar_hash' => null,
'avatar_mime' => null,
'avatar_updated_at' => Carbon::now(),
]
);
}
private function storeFromBinary(int $userId, string $binary, string $position = 'center'): string
{
$image = $this->readImageFromBinary($binary);
$image = $this->normalizeImage($image);
$cropPosition = $this->normalizePosition($position);
$diskName = (string) config('avatars.disk', 's3');
$disk = Storage::disk($diskName);
@@ -87,7 +115,7 @@ class AvatarService
$hashSeed = '';
foreach ($this->sizes as $size) {
$variant = $image->cover($size, $size);
$variant = $image->cover($size, $size, $cropPosition);
$encoded = (string) $variant->encode(new WebpEncoder($this->quality));
$disk->put("{$basePath}/{$size}.webp", $encoded, [
'visibility' => 'public',
@@ -110,6 +138,17 @@ class AvatarService
return $hash;
}
private function normalizePosition(string $position): string
{
$normalized = strtolower(trim($position));
if (in_array($normalized, self::ALLOWED_POSITIONS, true)) {
return $normalized;
}
return 'center';
}
private function normalizeImage($image)
{
try {

View File

@@ -18,21 +18,22 @@ class ThumbnailService
}
/**
* Canonical size keys (upload-agent spec §8): thumb · sq · md · lg · xl
* 'sm' is kept as a backwards-compatible alias for 'thumb'.
* Canonical size keys: xs · sm · md · lg · xl (+ legacy thumb/sq support).
*/
protected const VALID_SIZES = ['thumb', 'sq', 'sm', 'md', 'lg', 'xl'];
protected const VALID_SIZES = ['xs', 'sm', 'md', 'lg', 'xl', 'thumb', 'sq'];
/** Size aliases: legacy 'sm' maps to the 'thumb' CDN directory. */
protected const SIZE_ALIAS = ['sm' => 'thumb'];
/** Size aliases for backwards compatibility with old callers. */
protected const SIZE_ALIAS = [];
protected const THUMB_SIZES = [
'thumb' => ['height' => 320, 'quality' => 78, 'dir' => 'thumb'],
'xs' => ['height' => 160, 'quality' => 74, 'dir' => 'xs'],
'sm' => ['height' => 320, 'quality' => 78, 'dir' => 'sm'],
'sq' => ['height' => 512, 'quality' => 82, 'dir' => 'sq', 'square' => true],
'sm' => ['height' => 320, 'quality' => 78, 'dir' => 'thumb'], // alias for thumb
'md' => ['height' => 1024, 'quality' => 82, 'dir' => 'md'],
'lg' => ['height' => 1920, 'quality' => 85, 'dir' => 'lg'],
'xl' => ['height' => 2560, 'quality' => 90, 'dir' => 'xl'],
// Legacy compatibility for older paths still expecting /thumb/.
'thumb' => ['height' => 320, 'quality' => 78, 'dir' => 'thumb'],
];
/**

View File

@@ -129,8 +129,25 @@ final class UploadPipelineService
$height = is_array($dimensions) && isset($dimensions[1]) ? (int) $dimensions[1] : 1;
$origExt = strtolower(pathinfo($originalPath, PATHINFO_EXTENSION) ?: '');
$downloadFileName = $origFilename;
if (is_string($originalFileName) && trim($originalFileName) !== '') {
$candidate = basename(str_replace('\\', '/', $originalFileName));
$candidate = preg_replace('/[\x00-\x1F\x7F]/', '', (string) $candidate) ?? '';
$candidate = trim((string) $candidate);
if ($candidate !== '') {
$candidateExt = strtolower((string) pathinfo($candidate, PATHINFO_EXTENSION));
if ($candidateExt === '' && $origExt !== '') {
$candidate .= '.' . $origExt;
}
$downloadFileName = $candidate;
}
}
Artwork::query()->whereKey($artworkId)->update([
'file_name' => $origFilename,
'file_name' => $downloadFileName,
'file_path' => '',
'file_size' => (int) filesize($originalPath),
'mime_type' => $origMime,