resolvePath($file); $extension = $this->resolveExtension($file, $sourcePath); $this->assertSupported($extension); if (! in_array($extension, self::IMAGE_EXTENSIONS, true)) { throw new InvalidArgumentException('Nur Bilddateien koennen mit convertImage verarbeitet werden.'); } $this->assertSize($file, $sourcePath); $filename = Str::uuid()->toString().'.jpg'; $relativePath = 'slides/'.$filename; $targetPath = Storage::disk('public')->path($relativePath); Storage::disk('public')->makeDirectory('slides'); $this->ensureDirectory(dirname($targetPath)); $manager = $this->createImageManager(); $image = $manager->read($sourcePath); $originalWidth = $image->width(); $originalHeight = $image->height(); $warnings = $this->checkImageDimensions($originalWidth, $originalHeight); $image->contain(1920, 1080, '000000', 'center'); $image->save($targetPath, quality: 90); $thumbnailPath = $this->generateThumbnail($relativePath); return [ 'filename' => $relativePath, 'thumbnail' => $thumbnailPath, 'warnings' => $warnings, ]; } public function convertPowerPoint(UploadedFile|string|SplFileInfo $file): string { $sourcePath = $this->resolvePath($file); $extension = $this->resolveExtension($file, $sourcePath); $this->assertSupported($extension); if (! in_array($extension, self::POWERPOINT_EXTENSIONS, true)) { throw new InvalidArgumentException('Nur PPT/PPTX-Dateien sind hier erlaubt.'); } $this->assertSize($file, $sourcePath); $jobId = Str::uuid()->toString(); ConvertPowerPointJob::dispatch($sourcePath, $jobId); return $jobId; } public function processZip(UploadedFile|string|SplFileInfo $file): array { $sourcePath = $this->resolvePath($file); $extension = $this->resolveExtension($file, $sourcePath); $this->assertSupported($extension); if ($extension !== 'zip') { throw new InvalidArgumentException('processZip erwartet eine ZIP-Datei.'); } $this->assertSize($file, $sourcePath); $zip = new ZipArchive; if ($zip->open($sourcePath) !== true) { throw new InvalidArgumentException('ZIP-Datei konnte nicht geoeffnet werden.'); } $extractDir = storage_path('app/temp/zip-'.Str::uuid()->toString()); if (! is_dir($extractDir) && ! mkdir($extractDir, 0775, true) && ! is_dir($extractDir)) { throw new InvalidArgumentException('Temporaires ZIP-Verzeichnis konnte nicht erstellt werden.'); } $zip->extractTo($extractDir); $zip->close(); $results = []; $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($extractDir, RecursiveDirectoryIterator::SKIP_DOTS) ); foreach ($iterator as $entry) { if (! $entry instanceof SplFileInfo || ! $entry->isFile()) { continue; } $entryPath = $entry->getRealPath(); if ($entryPath === false) { continue; } $entryExtension = $this->extensionFromPath($entryPath); if (! in_array($entryExtension, self::SUPPORTED_EXTENSIONS, true)) { continue; } if (in_array($entryExtension, self::IMAGE_EXTENSIONS, true)) { $results[] = $this->convertImage($entryPath); continue; } if (in_array($entryExtension, self::POWERPOINT_EXTENSIONS, true)) { $results[] = ['job_id' => $this->convertPowerPoint($entryPath)]; continue; } $results = [...$results, ...$this->processZip($entryPath)]; } $this->deleteDirectory($extractDir); return $results; } public function generateThumbnail(string $path): string { $absolutePath = str_starts_with($path, DIRECTORY_SEPARATOR) ? $path : Storage::disk('public')->path($path); if (! is_file($absolutePath)) { throw new InvalidArgumentException('Datei fuer Thumbnail nicht gefunden.'); } $filename = pathinfo($absolutePath, PATHINFO_FILENAME).'.jpg'; $thumbnailRelativePath = 'slides/thumbnails/'.$filename; $thumbnailAbsolutePath = Storage::disk('public')->path($thumbnailRelativePath); Storage::disk('public')->makeDirectory('slides/thumbnails'); $this->ensureDirectory(dirname($thumbnailAbsolutePath)); $manager = $this->createImageManager(); $canvas = $manager->create(320, 180)->fill('000000'); $image = $manager->read($absolutePath); $image->scaleDown(width: 320, height: 180); $canvas->place($image, 'center'); $canvas->save($thumbnailAbsolutePath, quality: 85); return $thumbnailRelativePath; } private function resolvePath(UploadedFile|string|SplFileInfo $file): string { if ($file instanceof UploadedFile) { $path = $file->getRealPath(); if ($path === false || ! is_file($path)) { throw new InvalidArgumentException('Upload-Datei ist ungueltig.'); } return $path; } if ($file instanceof SplFileInfo) { $path = $file->getRealPath(); if ($path === false) { throw new InvalidArgumentException('Dateipfad ist ungueltig.'); } return $path; } if (! is_file($file)) { throw new InvalidArgumentException('Datei wurde nicht gefunden.'); } return $file; } private function assertSupported(string $extension): void { if (! in_array($extension, self::SUPPORTED_EXTENSIONS, true)) { throw new InvalidArgumentException('Dateityp wird nicht unterstuetzt.'); } } private function extensionFromPath(string $path): string { return strtolower((string) pathinfo($path, PATHINFO_EXTENSION)); } private function resolveExtension(UploadedFile|string|SplFileInfo $file, string $sourcePath): string { if ($file instanceof UploadedFile) { return strtolower($file->getClientOriginalExtension()); } return $this->extensionFromPath($sourcePath); } private function assertSize(UploadedFile|string|SplFileInfo $file, string $resolvedPath): void { $size = $file instanceof UploadedFile ? ($file->getSize() ?? 0) : (filesize($resolvedPath) ?: 0); if ($size > self::MAX_FILE_SIZE_BYTES) { throw new InvalidArgumentException('Datei ist groesser als 50MB.'); } } private function deleteDirectory(string $directory): void { if (! is_dir($directory)) { return; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $item) { if (! $item instanceof SplFileInfo) { continue; } if ($item->isDir()) { @rmdir($item->getPathname()); continue; } @unlink($item->getPathname()); } @rmdir($directory); } /** @return string[] */ private function checkImageDimensions(int $width, int $height): array { $warnings = []; $isExactOrLarger16by9 = $width >= 1920 && $height >= 1080 && abs($width / $height - 16 / 9) < 0.01; if (! $isExactOrLarger16by9) { $warnings[] = 'Das Bild hat nicht das optimale Seitenverhältnis von 16:9. ' .'Für die beste Darstellung verwende bitte Bilder mit exakt 1920×1080 Pixeln. ' .'Das Bild wurde trotzdem verarbeitet, es können aber schwarze Ränder entstehen.'; } if ($width < 1920 || $height < 1080) { $warnings[] = "Das Bild ({$width}×{$height}) ist kleiner als 1920×1080 und wurde hochskaliert. " .'Dadurch kann die Qualität schlechter sein. ' .'Lade am besten Bilder mit mindestens 1920×1080 Pixeln hoch.'; } return $warnings; } private function ensureDirectory(string $directory): void { if (is_dir($directory)) { return; } if (! mkdir($directory, 0775, true) && ! is_dir($directory)) { throw new InvalidArgumentException('Zielverzeichnis konnte nicht erstellt werden.'); } } private function createImageManager(): mixed { $managerClass = implode('\\', ['Intervention', 'Image', 'ImageManager']); $driverClass = implode('\\', ['Intervention', 'Image', 'Drivers', 'Gd', 'Driver']); return new $managerClass(new $driverClass); } }