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

105 lines
4.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\FileManager\Model;
use Esun\FileManager\Model\ResourceModel\File as FileResource;
use Esun\FileManager\Model\Security\FilePolicy;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\StoreManagerInterface;
class UploadService
{
private WriteInterface $mediaDirectory;
public function __construct(
Filesystem $filesystem,
private readonly Config $config,
private readonly FilePolicy $filePolicy,
private readonly Filename $filename,
private readonly FileUploaderFactory $uploaderFactory,
private readonly FileFactory $fileFactory,
private readonly FileResource $fileResource,
private readonly StoreManagerInterface $storeManager,
private readonly TimezoneInterface $timezone
) {
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
}
/**
* @return array{id: int, filename: string, url: string}
* @throws LocalizedException
*/
public function upload(array $uploadedFile): array
{
if (!$this->config->isEnabled()) {
throw new LocalizedException(__('File Manager is disabled.'));
}
$validated = $this->filePolicy->validate($uploadedFile);
$safeName = $this->filename->sanitize(
(string) ($uploadedFile['name'] ?? ''),
$validated['extension']
);
$relativeDirectory = $this->config->getUploadPath() . '/'
. $this->timezone->date()->format('Y/m');
$this->assertSafeRelativePath($relativeDirectory);
$this->mediaDirectory->create($relativeDirectory);
$uploader = $this->uploaderFactory->create(['fileId' => $uploadedFile]);
$uploader->setAllowedExtensions($this->config->getAllowedExtensions());
$uploader->setAllowRenameFiles(true);
$uploader->setFilesDispersion(false);
$result = $uploader->save($this->mediaDirectory->getAbsolutePath($relativeDirectory), $safeName);
if (!$result || empty($result['file'])) {
throw new LocalizedException(__('The file could not be saved.'));
}
$storedName = ltrim((string) $result['file'], '/');
$relativePath = $relativeDirectory . '/' . $storedName;
$this->assertSafeRelativePath($relativePath);
$url = rtrim(
$this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA),
'/'
) . '/' . str_replace('%2F', '/', rawurlencode($relativePath));
try {
$model = $this->fileFactory->create();
$model->setData([
'filename' => $storedName,
'filepath' => $relativePath,
'url' => $url,
'extension' => $validated['extension'],
'mime_type' => $validated['mime_type'],
'size' => (int) ($uploadedFile['size'] ?? 0),
]);
$this->fileResource->save($model);
} catch (\Throwable $exception) {
if ($this->mediaDirectory->isFile($relativePath)) {
$this->mediaDirectory->delete($relativePath);
}
throw new LocalizedException(__('The file record could not be saved.'), $exception);
}
return ['id' => (int) $model->getId(), 'filename' => $storedName, 'url' => $url];
}
/**
* @throws LocalizedException
*/
private function assertSafeRelativePath(string $path): void
{
$normalized = str_replace('\\', '/', $path);
if ($normalized !== trim($normalized, '/') || str_contains($normalized, '..')
|| !preg_match('#^uploads(?:/[\p{L}\p{N}._-]+)+$#u', $normalized)
) {
throw new LocalizedException(__('The generated upload path is invalid.'));
}
}
}