sanitizeOriginalName((string)$file->getClientOriginalName()); $storedFilename = $this->buildStoredFilename($originalName); $relativePath = $this->buildUniquePath($path, $storedFilename); try { Storage::disk('public')->put($relativePath, $file->getContent()); } catch (\Throwable $e) { throw new RuntimeException('Не удалось сохранить файл. Проверьте имя файла и повторите попытку.', 0, $e); } return File::query()->create([ 'link' => url('/storage/' . $relativePath), 'path' => $relativePath, 'user_id' => auth()->user()->id, 'original_name' => $originalName, 'mime_type' => $file->getClientMimeType(), ]); } private function sanitizeOriginalName(string $originalName): string { $name = basename(str_replace('\\', '/', $originalName)); if (class_exists('\Normalizer')) { $normalized = \Normalizer::normalize($name, \Normalizer::FORM_C); if (is_string($normalized)) { $name = $normalized; } } // Remove control/format unicode symbols (e.g. zero-width, LRM/RLM), then normalize spaces. $name = preg_replace('/\p{C}+/u', '', $name) ?? ''; $name = preg_replace('/\p{Z}+/u', ' ', $name) ?? ''; $name = trim($name); return $name === '' ? 'file' : $name; } private function buildStoredFilename(string $originalName): string { $baseName = pathinfo($originalName, PATHINFO_FILENAME); $extension = pathinfo($originalName, PATHINFO_EXTENSION); $baseName = preg_replace('/[<>:|?*\/\\\\]+/u', '_', $baseName) ?? ''; $baseName = preg_replace('/\s+/u', ' ', $baseName) ?? ''; $baseName = trim($baseName, " .\t\n\r\0\x0B"); if ($baseName === '' || $baseName === '.' || $baseName === '..') { $baseName = 'file'; } if (function_exists('mb_substr')) { $baseName = mb_substr($baseName, 0, 150); } else { $baseName = substr($baseName, 0, 150); } $extension = preg_replace('/[^A-Za-z0-9]+/', '', $extension) ?? ''; return $extension === '' ? $baseName : $baseName . '.' . $extension; } private function buildUniquePath(string $directory, string $filename): string { $directory = trim($directory, '/'); $basePath = $directory === '' ? $filename : $directory . '/' . $filename; if (!Storage::disk('public')->exists($basePath)) { return $basePath; } $baseName = pathinfo($filename, PATHINFO_FILENAME); $extension = pathinfo($filename, PATHINFO_EXTENSION); for ($index = 1; $index < 10000; $index++) { $candidateName = $baseName . ' (' . $index . ')'; if ($extension !== '') { $candidateName .= '.' . $extension; } $candidatePath = $directory === '' ? $candidateName : $directory . '/' . $candidateName; if (!Storage::disk('public')->exists($candidatePath)) { return $candidatePath; } } return $basePath; } /** * @param string $path * @param string $archiveName * @param int|null $userId * @return File * @throws Exception */ public function createZipArchive(string $path, string $archiveName, int $userId = null): File { if (!($tempFile = tempnam(sys_get_temp_dir(), Str::random()))) { throw new Exception('Cant create temporary file!'); } $zip = new ZipArchive(); $fullPath = storage_path('app/public/' . $path); $zip->open($tempFile, ZipArchive::CREATE | ZipArchive::OVERWRITE); $files = new RecursiveIteratorIterator( new \RecursiveDirectoryIterator($fullPath), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($files as $file) { $filePath = $file->getRealPath(); $relativePath = substr($filePath, strlen($fullPath) + 1); if(!$file->isDir()) { $zip->addFile($filePath, $relativePath); } else { $zip->addEmptyDir($relativePath); } } $zip->close(); $fileModel = File::query()->updateOrCreate([ 'link' => url('/storage/') . '/' . Str::replace('/tmp', '', $path) . '/' .$archiveName, 'path' => $path . '/' .$archiveName, 'user_id' => $userId ?? auth()->user()->id, 'original_name' => $archiveName, 'mime_type' => 'application/zip', ]); $contents = file_get_contents($tempFile); Storage::disk('public')->put($path . '/../' .$archiveName, $contents); unlink($tempFile); return $fileModel; } }