aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRon Lucke <lucke@elan-ev.de>2024-10-01 13:43:23 +0000
committerRon Lucke <lucke@elan-ev.de>2024-10-01 13:43:23 +0000
commit330ae49230c365b1e6a5b148c82dab0c1f4c69dd (patch)
tree0a90ff0774821527ed7ce74d7afb5ee71636c99c /lib
parent8bc88f75ea82dcdcf218e5114d12f72d6b37cf3b (diff)
Archiv Upload für Bilder-Pool
Closes #4056 Merge request studip/studip!2904
Diffstat (limited to 'lib')
-rw-r--r--lib/classes/JsonApi/RouteMap.php1
-rw-r--r--lib/classes/JsonApi/Routes/StockImages/Authority.php6
-rw-r--r--lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php2
-rw-r--r--lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php4
-rw-r--r--lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php59
-rw-r--r--lib/classes/JsonApi/Routes/StockImages/StockImagesZipUpload.php126
-rw-r--r--lib/classes/JsonApi/Routes/StockImages/UploadHelpers.php62
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