Files
2026-03-18 17:19:03 +08:00

261 lines
9.1 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\library;
use ba\Random;
use ba\Filesystem;
use app\common\model\Attachment;
use app\common\library\upload\Driver;
use app\common\library\upload\WebmanUploadedFile;
use InvalidArgumentException;
use RuntimeException;
/**
* 上传Webman 迁移版,使用 WebmanUploadedFile
*/
class Upload
{
protected array $config = [];
protected ?WebmanUploadedFile $file = null;
protected bool $isImage = false;
protected array $fileInfo = [];
protected array $driver = [
'name' => 'local',
'handler' => [],
'namespace' => '\\app\\common\\library\\upload\\driver\\',
];
protected string $topic = 'default';
public function __construct(?WebmanUploadedFile $file = null, array $config = [])
{
$upload = config('upload', []);
$this->config = array_merge($upload, $config);
if ($file) {
$this->setFile($file);
}
}
public function setFile(?WebmanUploadedFile $file): self
{
if (empty($file)) {
throw new RuntimeException(__('No files were uploaded'));
}
$suffix = strtolower($file->extension());
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
$this->fileInfo = [
'suffix' => $suffix,
'type' => $file->getMime(),
'size' => $file->getSize(),
'name' => $file->getOriginalName(),
'sha1' => $file->sha1(),
];
$this->file = $file;
return $this;
}
public function setDriver(string $driver): self
{
$this->driver['name'] = $driver;
return $this;
}
public function getDriver(?string $driver = null, bool $noDriveException = true): Driver|false
{
$driver = $driver ?? $this->driver['name'];
if (!isset($this->driver['handler'][$driver])) {
$class = $this->resolveDriverClass($driver);
if ($class) {
$this->driver['handler'][$driver] = new $class();
} elseif ($noDriveException) {
throw new InvalidArgumentException(__('Driver %s not supported', [$driver]));
}
}
return $this->driver['handler'][$driver] ?? false;
}
protected function resolveDriverClass(string $driver): string|false
{
if ($this->driver['namespace'] || str_contains($driver, '\\')) {
$class = str_contains($driver, '\\') ? $driver : $this->driver['namespace'] . $this->studly($driver);
if (class_exists($class)) {
return $class;
}
}
return false;
}
protected function studly(string $value): string
{
$value = ucwords(str_replace(['-', '_'], ' ', $value));
return str_replace(' ', '', $value);
}
public function setTopic(string $topic): self
{
$this->topic = $topic;
return $this;
}
protected function checkIsImage(): bool
{
if (in_array($this->fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp'])
|| in_array($this->fileInfo['suffix'], ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) {
$path = $this->file->getPathname();
$imgInfo = is_file($path) ? getimagesize($path) : false;
if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
throw new RuntimeException(__('The uploaded image file is not a valid image'));
}
$this->fileInfo['width'] = $imgInfo[0];
$this->fileInfo['height'] = $imgInfo[1];
$this->isImage = true;
return true;
}
return false;
}
public function isImage(): bool
{
return $this->isImage;
}
public function getSuffix(): string
{
return $this->fileInfo['suffix'] ?? 'file';
}
public function getSaveName(?string $saveName = null, ?string $filename = null, ?string $sha1 = null): string
{
if ($filename) {
$suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
} else {
$suffix = $this->fileInfo['suffix'];
}
$filename = $filename ?? $this->fileInfo['name'];
$sha1 = $sha1 ?? $this->fileInfo['sha1'];
$replaceArr = [
'{topic}' => $this->topic,
'{year}' => date('Y'),
'{mon}' => date('m'),
'{day}' => date('d'),
'{hour}' => date('H'),
'{min}' => date('i'),
'{sec}' => date('s'),
'{random}' => Random::build(),
'{random32}' => Random::build('alnum', 32),
'{fileName}' => $this->getFileNameSubstr($filename, $suffix),
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{fileSha1}' => $sha1,
];
$saveName = $saveName ?? $this->config['save_name'];
return Filesystem::fsFit(str_replace(array_keys($replaceArr), array_values($replaceArr), $saveName));
}
public function validates(): void
{
if (empty($this->file)) {
throw new RuntimeException(__('No files have been uploaded or the file size exceeds the upload limit of the server'));
}
$size = Filesystem::fileUnitToByte($this->config['max_size'] ?? '10M');
$mime = $this->checkConfig($this->config['allowed_mime_types'] ?? []);
$suffix = $this->checkConfig($this->config['allowed_suffixes'] ?? '');
if ($this->fileInfo['size'] > $size) {
throw new RuntimeException(__('The uploaded file is too large (%sMiB), Maximum file size:%sMiB', [
round($this->fileInfo['size'] / pow(1024, 2), 2),
round($size / pow(1024, 2), 2)
]));
}
if ($suffix && !in_array(strtolower($this->fileInfo['suffix']), $suffix)) {
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
if ($mime && !in_array(strtolower($this->fileInfo['type']), $mime)) {
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $this->topic)) {
throw new RuntimeException(__('Topic format error'));
}
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $this->driver['name'])) {
throw new RuntimeException(__('Driver %s not supported', [$this->driver['name']]));
}
if ($this->checkIsImage()) {
$maxW = $this->config['image_max_width'] ?? 0;
$maxH = $this->config['image_max_height'] ?? 0;
if ($maxW && $this->fileInfo['width'] > $maxW) {
throw new RuntimeException(__('The uploaded image file is not a valid image'));
}
if ($maxH && $this->fileInfo['height'] > $maxH) {
throw new RuntimeException(__('The uploaded image file is not a valid image'));
}
}
}
public function upload(?string $saveName = null, int $adminId = 0, int $userId = 0): array
{
$this->validates();
$driver = $this->getDriver();
if (!$driver) {
throw new RuntimeException(__('Driver %s not supported', [$this->driver['name']]));
}
$saveName = $saveName ?: $this->getSaveName();
$params = [
'topic' => $this->topic,
'admin_id' => $adminId,
'user_id' => $userId,
'url' => $driver->url($saveName, false),
'width' => $this->fileInfo['width'] ?? 0,
'height' => $this->fileInfo['height'] ?? 0,
'name' => $this->getFileNameSubstr($this->fileInfo['name'], $this->fileInfo['suffix'], 100) . '.' . $this->fileInfo['suffix'],
'size' => $this->fileInfo['size'],
'mimetype' => $this->fileInfo['type'],
'storage' => $this->driver['name'],
'sha1' => $this->fileInfo['sha1'],
];
$attachment = Attachment::where('sha1', $params['sha1'])
->where('topic', $params['topic'])
->where('storage', $params['storage'])
->find();
if ($attachment && $driver->exists($attachment->url)) {
$attachment->quote++;
$attachment->last_upload_time = time();
} else {
$driver->save($this->file, $saveName);
$attachment = new Attachment();
$attachment->data(array_filter($params));
}
$attachment->save();
return $attachment->toArray();
}
public function getFileNameSubstr(string $fileName, string $suffix, int $length = 15): string
{
$pattern = "/[\s:@#?&\/=',+]+/u";
$fileName = str_replace(".$suffix", '', $fileName);
$fileName = preg_replace($pattern, '', $fileName);
return mb_substr(htmlspecialchars(strip_tags($fileName)), 0, $length);
}
protected function checkConfig(mixed $configItem): array
{
if (is_array($configItem)) {
return array_map('strtolower', $configItem);
}
return $configItem ? explode(',', strtolower((string)$configItem)) : [];
}
}