pp-planer/app/Services/FileConversionService.php
Thorsten Bus 04d271f96a style: apply Laravel Pint formatting across codebase
Auto-formatted by Laravel Pint (default Laravel preset): string
concatenation spacing, anonymous class brace placement, constructor
body shorthand, import ordering, and assertion indentation.
2026-03-02 23:02:03 +01:00

274 lines
8.6 KiB
PHP

<?php
namespace App\Services;
use App\Jobs\ConvertPowerPointJob;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use ZipArchive;
class FileConversionService
{
private const MAX_FILE_SIZE_BYTES = 52428800;
private const SUPPORTED_EXTENSIONS = ['png', 'jpg', 'jpeg', 'ppt', 'pptx', 'zip'];
private const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg'];
private const POWERPOINT_EXTENSIONS = ['ppt', 'pptx'];
public function convertImage(UploadedFile|string|SplFileInfo $file): array
{
$sourcePath = $this->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();
$canvas = $manager->create(1920, 1080)->fill('000000');
$image = $manager->read($sourcePath);
$image->scaleDown(width: 1920, height: 1080);
$canvas->place($image, 'center');
$canvas->save($targetPath, quality: 90);
$thumbnailPath = $this->generateThumbnail($relativePath);
return [
'filename' => $relativePath,
'thumbnail' => $thumbnailPath,
];
}
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);
}
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);
}
}