diff options
| author | Ron Lucke <lucke@elan-ev.de> | 2024-10-01 13:43:23 +0000 |
|---|---|---|
| committer | Ron Lucke <lucke@elan-ev.de> | 2024-10-01 13:43:23 +0000 |
| commit | 330ae49230c365b1e6a5b148c82dab0c1f4c69dd (patch) | |
| tree | 0a90ff0774821527ed7ce74d7afb5ee71636c99c /lib | |
| parent | 8bc88f75ea82dcdcf218e5114d12f72d6b37cf3b (diff) | |
Archiv Upload für Bilder-Pool
Closes #4056
Merge request studip/studip!2904
Diffstat (limited to 'lib')
7 files changed, 199 insertions, 61 deletions
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index 4b61b3d..579b4e9 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -649,6 +649,7 @@ class RouteMap $group->delete('/stock-images/{id}', Routes\StockImages\StockImagesDelete::class); $group->post('/stock-images/{id}/blob', Routes\StockImages\StockImagesUpload::class); + $group->post('/stock-images/zip', Routes\StockImages\StockImagesZipUpload::class); } private function addAuthenticatedAvatarRoutes(RouteCollectorProxy $group): void diff --git a/lib/classes/JsonApi/Routes/StockImages/Authority.php b/lib/classes/JsonApi/Routes/StockImages/Authority.php index 77851fd..8b4b537 100644 --- a/lib/classes/JsonApi/Routes/StockImages/Authority.php +++ b/lib/classes/JsonApi/Routes/StockImages/Authority.php @@ -34,7 +34,7 @@ class Authority /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public static function canUpdateStockImage(User $user, StockImage $resource): bool + public static function canUpdateStockImage(User $user): bool { return self::canCreateStockImage($user); } @@ -42,7 +42,7 @@ class Authority /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public static function canUploadStockImage(User $user, StockImage $resource): bool + public static function canUploadStockImage(User $user): bool { return self::canCreateStockImage($user); } @@ -50,7 +50,7 @@ class Authority /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public static function canDeleteStockImage(User $user, StockImage $resource): bool + public static function canDeleteStockImage(User $user): bool { return self::canCreateStockImage($user); } diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php index e0b5468..4487f4a 100644 --- a/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php @@ -22,7 +22,7 @@ class StockImagesDelete extends JsonApiController throw new RecordNotFoundException(); } - if (!Authority::canDeleteStockImage($this->getUser($request), $resource)) { + if (!Authority::canDeleteStockImage($this->getUser($request))) { throw new AuthorizationFailedException(); } $resource->delete(); diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php index e523705..942f7fd 100644 --- a/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php @@ -30,9 +30,9 @@ class StockImagesUpdate extends JsonApiController throw new RecordNotFoundException(); } - $json = $this->validate($request, $resource); + $json = $this->validate($request); $user = $this->getUser($request); - if (!Authority::canUpdateStockImage($user, $resource)) { + if (!Authority::canUpdateStockImage($user)) { throw new AuthorizationFailedException(); } $resource = $this->updateResource($resource, $json); diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php index c423117..6cda466 100644 --- a/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php @@ -14,6 +14,7 @@ use Studip\StockImages\PaletteCreator; class StockImagesUpload extends NonJsonApiController { + use UploadHelpers; /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -24,7 +25,7 @@ class StockImagesUpload extends NonJsonApiController throw new RecordNotFoundException(); } - if (!Authority::canUploadStockImage($this->getUser($request), $resource)) { + if (!Authority::canUploadStockImage($this->getUser($request))) { throw new AuthorizationFailedException(); } @@ -36,9 +37,9 @@ class StockImagesUpload extends NonJsonApiController private function handleUpload(Request $request, \StockImage $resource): void { - $uploadedFile = $this->getUploadedFile($request); + $uploadedFile = self::getUploadedFile($request); if (UPLOAD_ERR_OK !== $uploadedFile->getError()) { - $error = $this->getErrorString($uploadedFile->getError()); + $error = self::getErrorString($uploadedFile->getError()); throw new BadRequestException($error); } @@ -58,58 +59,6 @@ class StockImagesUpload extends NonJsonApiController $resource->store(); } - private function getUploadedFile(Request $request): UploadedFileInterface - { - $files = iterator_to_array($this->getUploadedFiles($request)); - - if (0 === count($files)) { - throw new BadRequestException('File upload required.'); - } - - if (count($files) > 1) { - throw new BadRequestException('Multiple file upload not possible.'); - } - - $uploadedFile = reset($files); - if (UPLOAD_ERR_OK !== $uploadedFile->getError()) { - throw new BadRequestException('Upload error.'); - } - - return $uploadedFile; - } - - /** - * @return iterable<UploadedFileInterface> a list of uploaded files - */ - private function getUploadedFiles(Request $request): iterable - { - foreach ($request->getUploadedFiles() as $item) { - if (!is_array($item)) { - yield $item; - continue; - } - foreach ($item as $file) { - yield $file; - } - } - } - - private function getErrorString(int $errNo): string - { - $errors = [ - UPLOAD_ERR_OK => 'There is no error, the file uploaded with success', - UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', - UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', - UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', - UPLOAD_ERR_NO_FILE => 'No file was uploaded', - UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder', - UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', - UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', - ]; - - return $errors[$errNo] ?? ''; - } - /** * @return string|null null, if the file is valid, otherwise a string containing the error */ diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesZipUpload.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesZipUpload.php new file mode 100644 index 0000000..9cf24c7 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesZipUpload.php @@ -0,0 +1,126 @@ +<?php + +namespace JsonApi\Routes\StockImages; + +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\BadRequestException; +use JsonApi\NonJsonApiController; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Nyholm\Psr7\UploadedFile; +use Studip\StockImages\Scaler; +use Studip\StockImages\PaletteCreator; + +class StockImagesZipUpload extends NonJsonApiController +{ + use UploadHelpers; + + public function __invoke(Request $request, Response $response, $args): Response + { + if (!Authority::canUploadStockImage($this->getUser($request))) { + throw new AuthorizationFailedException(); + } + $image_count = $this->handleUpload($request); + + $response = $response->withHeader('Content-Type', 'application/json'); + $response->getBody()->write(json_encode(['image-count' => $image_count])); + + return $response; + } + + private function handleUpload(Request $request): int + { + $uploadedFile = self::getUploadedFile($request); + if (UPLOAD_ERR_OK !== $uploadedFile->getError()) { + $error = self::getErrorString($uploadedFile->getError()); + throw new BadRequestException($error); + } + + $validateError = self::validate($uploadedFile); + if (!empty($validateError)) { + throw new BadRequestException($validateError); + } + + $tmp_path = $GLOBALS['TMP_PATH'] . '/stock-images/'; + + if (!file_exists($tmp_path)) { + mkdir($tmp_path); + } + $zip_path = $tmp_path . 'archiv.zip'; + $uploadedFile->moveTo($zip_path); + $zip = new \ZipArchive; + if ($zip->open($zip_path) === TRUE) { + $zip->extractTo($tmp_path); + $zip->close(); + } else { + $this->cleanTmp($tmp_path); + throw new BadRequestException('Can not extract Zip file.'); + } + $csv_file = file($tmp_path . 'meta.csv'); + if (!$csv_file) { + $this->cleanTmp($tmp_path); + throw new BadRequestException('No meta.csv file provided.'); + } + $rows = array_map( + fn($v) => str_getcsv($v, ';'), + $csv_file + ); + $header = array_shift($rows); + $images = []; + foreach ($rows as $row) { + $images[] = array_combine($header, $row); + } + + $image_counter = 0; + foreach ($images as $i => $meta) { + $filename = $meta['filename']; + if (!$filename) { + continue; + } + $filepath = $tmp_path . $filename; + $filesize = filesize($filepath); + $imagesize = getimagesize($filepath); + + $image = \StockImage::create([ + 'title' => $meta['title'] ?? 'unknown', + 'description' => $meta['description'] ?? '', + 'license' => $meta['license'] ?? '', + 'author' => $meta['author'] ?? '', + 'height' => $imagesize[1], + 'width' => $imagesize[0], + 'mime_type' => $imagesize['mime'], + 'size' => $filesize, + 'tags' => json_encode(explode(',', $meta['tags'])), + ]); + + copy($filepath, $image->getPath()); + $scaler = new \Studip\StockImages\Scaler(); + $scaler($image); + $paletteCreator = new \Studip\StockImages\PaletteCreator(); + $paletteCreator($image); + + $image_counter++; + } + + $this->cleanTmp($tmp_path); + + return $image_counter; + } + + private function cleanTmp(string $tmp_path): void + { + array_map('unlink', glob("$tmp_path/*.*")); + rmdir($tmp_path); + } + + /** + * @return string|null null, if the file is valid, otherwise a string containing the error + */ + private function validate(UploadedFile $file) + { + $mimeType = $file->getClientMediaType(); + if (!in_array($mimeType, ['application/x-zip-compressed', ' application/x-zip', 'application/zip'])) { + return 'Unsupported archive type.'; + } + } +}
\ No newline at end of file diff --git a/lib/classes/JsonApi/Routes/StockImages/UploadHelpers.php b/lib/classes/JsonApi/Routes/StockImages/UploadHelpers.php new file mode 100644 index 0000000..31e98db --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/UploadHelpers.php @@ -0,0 +1,62 @@ +<?php + +namespace JsonApi\Routes\StockImages; + +use JsonApi\Errors\BadRequestException; +use Psr\Http\Message\ServerRequestInterface as Request; +use Nyholm\Psr7\UploadedFile; + +trait UploadHelpers +{ + protected static function getUploadedFile(Request $request): UploadedFile + { + $files = iterator_to_array(self::getUploadedFiles($request)); + + if (0 === count($files)) { + throw new BadRequestException('File upload required.'); + } + + if (count($files) > 1) { + throw new BadRequestException('Multiple file upload not possible.'); + } + + $uploadedFile = reset($files); + if (UPLOAD_ERR_OK !== $uploadedFile->getError()) { + throw new BadRequestException('Upload error.'); + } + + return $uploadedFile; + } + + /** + * @return iterable<UploadedFile> a list of uploaded files + */ + protected static function getUploadedFiles(Request $request): iterable + { + foreach ($request->getUploadedFiles() as $item) { + if (!is_array($item)) { + yield $item; + continue; + } + foreach ($item as $file) { + yield $file; + } + } + } + + protected static function getErrorString(int $errNo): string + { + $errors = [ + UPLOAD_ERR_OK => 'There is no error, the file uploaded with success', + UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', + UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', + UPLOAD_ERR_NO_FILE => 'No file was uploaded', + UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder', + UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', + UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', + ]; + + return $errors[$errNo] ?? ''; + } +}
\ No newline at end of file |
