v1.0.0.1
StocketBase
- <?php
-
- declare(strict_types=1);
-
- final class Avatar
- {
- private const DIR = 'media/avatars';
- private const MAX_BYTES = 2 * 1024 * 1024;
- private const MAX_DIMENSION = 6000;
- private const SIZE = 256;
- private const TYPES = [
- 'image/jpeg' => 'jpg',
- 'image/png' => 'png',
- 'image/webp' => 'webp',
- ];
-
- public static function hasUpload(?array $file): bool
- {
- return $file !== null && ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE;
- }
-
- public static function store(array $file): array
- {
- if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
- return [null, 'Photo upload failed.'];
- }
-
- if ((int) $file['size'] > self::MAX_BYTES) {
- return [null, 'Photo is too large (max 2MB).'];
- }
-
- $finfo = finfo_open(FILEINFO_MIME_TYPE);
- $mimeType = (string) finfo_file($finfo, $file['tmp_name']);
-
- $imageInfo = @getimagesize($file['tmp_name']);
- if (!isset(self::TYPES[$mimeType]) || $imageInfo === false) {
- return [null, 'Photo must be a JPG, PNG or WebP image.'];
- }
-
- if ($imageInfo[0] > self::MAX_DIMENSION || $imageInfo[1] > self::MAX_DIMENSION) {
- return [null, 'Photo dimensions are too large (max ' . self::MAX_DIMENSION . ' px).'];
- }
-
- $directory = dirname(__DIR__) . '/' . self::DIR;
- if (!is_dir($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) {
- return [null, 'Could not create the photos folder.'];
- }
-
- $baseName = bin2hex(random_bytes(16));
- $extension = self::resize($file['tmp_name'], $directory . '/' . $baseName);
-
- if ($extension === null) {
- $extension = self::TYPES[$mimeType];
- if (!move_uploaded_file($file['tmp_name'], $directory . '/' . $baseName . '.' . $extension)) {
- return [null, 'Could not save the photo.'];
- }
- }
-
- return [self::DIR . '/' . $baseName . '.' . $extension, null];
- }
-
- public static function delete(?string $path): void
- {
- if (!self::isValidPath($path)) {
- return;
- }
-
- $fullPath = dirname(__DIR__) . '/' . $path;
- if (is_file($fullPath)) {
- @unlink($fullPath);
- }
- }
-
- public static function html(array $account, string $class): string
- {
- $path = (string) ($account['avatar_path'] ?? '');
-
- if (self::isValidPath($path)) {
- return '<div class="' . htmlspecialchars($class, ENT_QUOTES) . ' has-image"><img src="' . htmlspecialchars($path, ENT_QUOTES) . '" alt=""></div>';
- }
-
- return '<div class="' . htmlspecialchars($class, ENT_QUOTES) . '">' . htmlspecialchars(self::initial((string) ($account['display_name'] ?? '?'))) . '</div>';
- }
-
- public static function initial(string $name): string
- {
- return mb_strtoupper(mb_substr($name !== '' ? $name : '?', 0, 1));
- }
-
- public static function isValidPath(?string $path): bool
- {
- return $path !== null
- && $path !== ''
- && str_starts_with($path, self::DIR . '/')
- && !str_contains($path, '..');
- }
-
- private static function resize(string $source, string $targetBase): ?string
- {
- if (!function_exists('imagecreatefromstring') || !function_exists('imagecreatetruecolor')) {
- return null;
- }
-
- $data = @file_get_contents($source);
- $image = $data !== false ? @imagecreatefromstring($data) : false;
- if ($image === false) {
- return null;
- }
-
- $width = imagesx($image);
- $height = imagesy($image);
- $side = min($width, $height);
-
- $canvas = imagecreatetruecolor(self::SIZE, self::SIZE);
- imagealphablending($canvas, false);
- imagesavealpha($canvas, true);
- imagefill($canvas, 0, 0, imagecolorallocatealpha($canvas, 0, 0, 0, 127));
- imagecopyresampled($canvas, $image, 0, 0, intdiv($width - $side, 2), intdiv($height - $side, 2), self::SIZE, self::SIZE, $side, $side);
-
- if (function_exists('imagewebp') && @imagewebp($canvas, $targetBase . '.webp', 85)) {
- return 'webp';
- }
-
- if (@imagepng($canvas, $targetBase . '.png', 6)) {
- return 'png';
- }
-
- return null;
- }
- }
-