Files
2026-09-15 10:35:56 +08:00

135 lines
5.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\CustomerMessage\Model;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem\Driver\File;
class AttachmentStorage
{
private const MAX_FILES = 5;
private const MAX_SIZE = 5242880;
private const MIME_EXTENSIONS = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
];
public function __construct(
private readonly DirectoryList $directoryList,
private readonly File $fileDriver
) {
}
public function saveUploads(array $files, int $threadId, int $entryId): array
{
$uploads = $this->normalize($files);
if (count($uploads) > self::MAX_FILES) {
throw new LocalizedException(__('You can upload up to %1 images per message.', self::MAX_FILES));
}
$saved = [];
try {
foreach ($uploads as $upload) {
if ((int)$upload['error'] === UPLOAD_ERR_NO_FILE) {
continue;
}
if ((int)$upload['error'] !== UPLOAD_ERR_OK) {
throw new LocalizedException(__('One of the images could not be uploaded.'));
}
if ((int)$upload['size'] <= 0 || (int)$upload['size'] > self::MAX_SIZE) {
throw new LocalizedException(__('Each image must be no larger than 5 MB.'));
}
$mime = (new \finfo(FILEINFO_MIME_TYPE))->file((string)$upload['tmp_name']);
if (!isset(self::MIME_EXTENSIONS[$mime])) {
throw new LocalizedException(__('Only JPG, PNG and WebP images are allowed.'));
}
$relativeDirectory = sprintf('esun_customer_message/%d/%d', $threadId, $entryId);
$absoluteDirectory = $this->directoryList->getPath(DirectoryList::VAR_DIR) . '/' . $relativeDirectory;
$this->fileDriver->createDirectory($absoluteDirectory, 0750);
$filename = bin2hex(random_bytes(20)) . '.' . self::MIME_EXTENSIONS[$mime];
$absolutePath = $absoluteDirectory . '/' . $filename;
if (!move_uploaded_file((string)$upload['tmp_name'], $absolutePath)) {
throw new LocalizedException(__('The image could not be stored.'));
}
chmod($absolutePath, 0640);
$saved[] = [
'original_name' => mb_substr(basename((string)$upload['name']), 0, 255),
'storage_path' => $relativeDirectory . '/' . $filename,
'mime_type' => $mime,
'file_size' => (int)$upload['size'],
];
}
} catch (\Throwable $exception) {
$this->delete(array_column($saved, 'storage_path'));
throw $exception;
}
return $saved;
}
public function hasUploads(array $files): bool
{
foreach ($this->normalize($files) as $upload) {
if ((int)$upload['error'] !== UPLOAD_ERR_NO_FILE) {
return true;
}
}
return false;
}
public function getAbsolutePath(string $relativePath): string
{
$base = $this->directoryList->getPath(DirectoryList::VAR_DIR) . '/esun_customer_message';
$path = $this->directoryList->getPath(DirectoryList::VAR_DIR) . '/' . ltrim($relativePath, '/');
$realBase = realpath($base);
$realPath = realpath($path);
if ($realBase === false || $realPath === false || !str_starts_with($realPath, $realBase . DIRECTORY_SEPARATOR)) {
throw new LocalizedException(__('The requested image is not available.'));
}
return $realPath;
}
public function delete(array $relativePaths): void
{
foreach ($relativePaths as $relativePath) {
try {
$path = $this->getAbsolutePath((string)$relativePath);
if ($this->fileDriver->isExists($path)) {
$this->fileDriver->deleteFile($path);
}
} catch (\Throwable) {
// Best-effort cleanup after a failed transaction.
}
}
}
private function normalize(array $files): array
{
if (!$files) {
return [];
}
if (isset($files['name']) && is_array($files['name'])) {
$result = [];
foreach ($files['name'] as $index => $name) {
$result[] = [
'name' => $name,
'type' => $files['type'][$index] ?? '',
'tmp_name' => $files['tmp_name'][$index] ?? '',
'error' => $files['error'][$index] ?? UPLOAD_ERR_NO_FILE,
'size' => $files['size'][$index] ?? 0,
];
}
return $result;
}
if (isset($files['name'])) {
return [$files];
}
return array_values(array_filter($files, 'is_array'));
}
}