'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)) : []; } }