Skip to content

Commit c4ec50d

Browse files
committed
ZIP Exports: Got zip format validation functionally complete
1 parent b50b7b6 commit c4ec50d

File tree

12 files changed

+149
-42
lines changed

12 files changed

+149
-42
lines changed

app/Exceptions/ZipExportValidationException.php

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/Exports/Controllers/ImportController.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace BookStack\Exports\Controllers;
44

5+
use BookStack\Exports\ZipExports\ZipExportValidator;
56
use BookStack\Http\Controller;
67
use Illuminate\Http\Request;
78

@@ -26,7 +27,13 @@ public function upload(Request $request)
2627
]);
2728

2829
$file = $request->file('file');
29-
$file->getRealPath();
30+
$zipPath = $file->getRealPath();
31+
32+
$errors = (new ZipExportValidator($zipPath))->validate();
33+
if ($errors) {
34+
dd($errors);
35+
}
36+
dd('passed');
3037
// TODO - Read existing ZIP upload and send through validator
3138
// TODO - If invalid, return user with errors
3239
// TODO - Upload to storage

app/Exports/ZipExports/Models/ZipExportAttachment.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ public static function validate(ZipValidationHelper $context, array $data): arra
4747
'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()],
4848
];
4949

50-
return $context->validateArray($data, $rules);
50+
return $context->validateData($data, $rules);
5151
}
5252
}

app/Exports/ZipExports/Models/ZipExportBook.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use BookStack\Entities\Models\Chapter;
77
use BookStack\Entities\Models\Page;
88
use BookStack\Exports\ZipExports\ZipExportFiles;
9+
use BookStack\Exports\ZipExports\ZipValidationHelper;
910

1011
class ZipExportBook extends ZipExportModel
1112
{
@@ -50,4 +51,24 @@ public static function fromModel(Book $model, ZipExportFiles $files): self
5051

5152
return $instance;
5253
}
54+
55+
public static function validate(ZipValidationHelper $context, array $data): array
56+
{
57+
$rules = [
58+
'id' => ['nullable', 'int'],
59+
'name' => ['required', 'string', 'min:1'],
60+
'description_html' => ['nullable', 'string'],
61+
'cover' => ['nullable', 'string', $context->fileReferenceRule()],
62+
'tags' => ['array'],
63+
'pages' => ['array'],
64+
'chapters' => ['array'],
65+
];
66+
67+
$errors = $context->validateData($data, $rules);
68+
$errors['tags'] = $context->validateRelations($data['tags'] ?? [], ZipExportTag::class);
69+
$errors['pages'] = $context->validateRelations($data['pages'] ?? [], ZipExportPage::class);
70+
$errors['chapters'] = $context->validateRelations($data['chapters'] ?? [], ZipExportChapter::class);
71+
72+
return $errors;
73+
}
5374
}

app/Exports/ZipExports/Models/ZipExportChapter.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Entities\Models\Chapter;
66
use BookStack\Entities\Models\Page;
77
use BookStack\Exports\ZipExports\ZipExportFiles;
8+
use BookStack\Exports\ZipExports\ZipValidationHelper;
89

910
class ZipExportChapter extends ZipExportModel
1011
{
@@ -42,4 +43,22 @@ public static function fromModelArray(array $chapterArray, ZipExportFiles $files
4243
return self::fromModel($chapter, $files);
4344
}, $chapterArray));
4445
}
46+
47+
public static function validate(ZipValidationHelper $context, array $data): array
48+
{
49+
$rules = [
50+
'id' => ['nullable', 'int'],
51+
'name' => ['required', 'string', 'min:1'],
52+
'description_html' => ['nullable', 'string'],
53+
'priority' => ['nullable', 'int'],
54+
'tags' => ['array'],
55+
'pages' => ['array'],
56+
];
57+
58+
$errors = $context->validateData($data, $rules);
59+
$errors['tags'] = $context->validateRelations($data['tags'] ?? [], ZipExportTag::class);
60+
$errors['pages'] = $context->validateRelations($data['pages'] ?? [], ZipExportPage::class);
61+
62+
return $errors;
63+
}
4564
}

app/Exports/ZipExports/Models/ZipExportImage.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace BookStack\Exports\ZipExports\Models;
44

55
use BookStack\Exports\ZipExports\ZipExportFiles;
6+
use BookStack\Exports\ZipExports\ZipValidationHelper;
67
use BookStack\Uploads\Image;
8+
use Illuminate\Validation\Rule;
79

810
class ZipExportImage extends ZipExportModel
911
{
@@ -22,4 +24,16 @@ public static function fromModel(Image $model, ZipExportFiles $files): self
2224

2325
return $instance;
2426
}
27+
28+
public static function validate(ZipValidationHelper $context, array $data): array
29+
{
30+
$rules = [
31+
'id' => ['nullable', 'int'],
32+
'name' => ['required', 'string', 'min:1'],
33+
'file' => ['required', 'string', $context->fileReferenceRule()],
34+
'type' => ['required', 'string', Rule::in(['gallery', 'drawio'])],
35+
];
36+
37+
return $context->validateData($data, $rules);
38+
}
2539
}

app/Exports/ZipExports/Models/ZipExportPage.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Entities\Models\Page;
66
use BookStack\Entities\Tools\PageContent;
77
use BookStack\Exports\ZipExports\ZipExportFiles;
8+
use BookStack\Exports\ZipExports\ZipValidationHelper;
89

910
class ZipExportPage extends ZipExportModel
1011
{
@@ -48,4 +49,25 @@ public static function fromModelArray(array $pageArray, ZipExportFiles $files):
4849
return self::fromModel($page, $files);
4950
}, $pageArray));
5051
}
52+
53+
public static function validate(ZipValidationHelper $context, array $data): array
54+
{
55+
$rules = [
56+
'id' => ['nullable', 'int'],
57+
'name' => ['required', 'string', 'min:1'],
58+
'html' => ['nullable', 'string'],
59+
'markdown' => ['nullable', 'string'],
60+
'priority' => ['nullable', 'int'],
61+
'attachments' => ['array'],
62+
'images' => ['array'],
63+
'tags' => ['array'],
64+
];
65+
66+
$errors = $context->validateData($data, $rules);
67+
$errors['attachments'] = $context->validateRelations($data['attachments'] ?? [], ZipExportAttachment::class);
68+
$errors['images'] = $context->validateRelations($data['images'] ?? [], ZipExportImage::class);
69+
$errors['tags'] = $context->validateRelations($data['tags'] ?? [], ZipExportTag::class);
70+
71+
return $errors;
72+
}
5173
}

app/Exports/ZipExports/Models/ZipExportTag.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ public static function validate(ZipValidationHelper $context, array $data): arra
3434
'order' => ['nullable', 'integer'],
3535
];
3636

37-
return $context->validateArray($data, $rules);
37+
return $context->validateData($data, $rules);
3838
}
3939
}

app/Exports/ZipExports/ZipExportValidator.php

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,69 @@
22

33
namespace BookStack\Exports\ZipExports;
44

5-
use BookStack\Exceptions\ZipExportValidationException;
5+
use BookStack\Exports\ZipExports\Models\ZipExportBook;
6+
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
7+
use BookStack\Exports\ZipExports\Models\ZipExportPage;
68
use ZipArchive;
79

810
class ZipExportValidator
911
{
10-
protected array $errors = [];
11-
1212
public function __construct(
1313
protected string $zipPath,
1414
) {
1515
}
1616

17-
/**
18-
* @throws ZipExportValidationException
19-
*/
20-
public function validate()
17+
public function validate(): array
2118
{
22-
// TODO - Return type
23-
// TODO - extract messages to translations?
24-
2519
// Validate file exists
2620
if (!file_exists($this->zipPath) || !is_readable($this->zipPath)) {
27-
$this->throwErrors("Could not read ZIP file");
21+
return ['format' => "Could not read ZIP file"];
2822
}
2923

3024
// Validate file is valid zip
3125
$zip = new \ZipArchive();
3226
$opened = $zip->open($this->zipPath, ZipArchive::RDONLY);
3327
if ($opened !== true) {
34-
$this->throwErrors("Could not read ZIP file");
28+
return ['format' => "Could not read ZIP file"];
3529
}
3630

3731
// Validate json data exists, including metadata
3832
$jsonData = $zip->getFromName('data.json') ?: '';
3933
$importData = json_decode($jsonData, true);
4034
if (!$importData) {
41-
$this->throwErrors("Could not decode ZIP data.json content");
35+
return ['format' => "Could not find and decode ZIP data.json content"];
4236
}
4337

38+
$helper = new ZipValidationHelper($zip);
39+
4440
if (isset($importData['book'])) {
45-
// TODO - Validate book
41+
$modelErrors = ZipExportBook::validate($helper, $importData['book']);
42+
$keyPrefix = 'book';
4643
} else if (isset($importData['chapter'])) {
47-
// TODO - Validate chapter
44+
$modelErrors = ZipExportChapter::validate($helper, $importData['chapter']);
45+
$keyPrefix = 'chapter';
4846
} else if (isset($importData['page'])) {
49-
// TODO - Validate page
47+
$modelErrors = ZipExportPage::validate($helper, $importData['page']);
48+
$keyPrefix = 'page';
5049
} else {
51-
$this->throwErrors("ZIP file has no book, chapter or page data");
50+
return ['format' => "ZIP file has no book, chapter or page data"];
5251
}
52+
53+
return $this->flattenModelErrors($modelErrors, $keyPrefix);
5354
}
5455

55-
/**
56-
* @throws ZipExportValidationException
57-
*/
58-
protected function throwErrors(...$errorsToAdd): never
56+
protected function flattenModelErrors(array $errors, string $keyPrefix): array
5957
{
60-
array_push($this->errors, ...$errorsToAdd);
61-
throw new ZipExportValidationException($this->errors);
58+
$flattened = [];
59+
60+
foreach ($errors as $key => $error) {
61+
if (is_array($error)) {
62+
$flattened = array_merge($flattened, $this->flattenModelErrors($error, $keyPrefix . '.' . $key));
63+
} else {
64+
$flattened[$keyPrefix . '.' . $key] = $error;
65+
}
66+
}
67+
68+
return $flattened;
6269
}
6370
}

app/Exports/ZipExports/ZipValidationHelper.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace BookStack\Exports\ZipExports;
44

5+
use BookStack\Exports\ZipExports\Models\ZipExportModel;
56
use Illuminate\Validation\Factory;
67
use ZipArchive;
78

@@ -15,9 +16,15 @@ public function __construct(
1516
$this->validationFactory = app(Factory::class);
1617
}
1718

18-
public function validateArray(array $data, array $rules): array
19+
public function validateData(array $data, array $rules): array
1920
{
20-
return $this->validationFactory->make($data, $rules)->errors()->messages();
21+
$messages = $this->validationFactory->make($data, $rules)->errors()->messages();
22+
23+
foreach ($messages as $key => $message) {
24+
$messages[$key] = implode("\n", $message);
25+
}
26+
27+
return $messages;
2128
}
2229

2330
public function zipFileExists(string $name): bool
@@ -29,4 +36,24 @@ public function fileReferenceRule(): ZipFileReferenceRule
2936
{
3037
return new ZipFileReferenceRule($this);
3138
}
39+
40+
/**
41+
* Validate an array of relation data arrays that are expected
42+
* to be for the given ZipExportModel.
43+
* @param class-string<ZipExportModel> $model
44+
*/
45+
public function validateRelations(array $relations, string $model): array
46+
{
47+
$results = [];
48+
49+
foreach ($relations as $key => $relationData) {
50+
if (is_array($relationData)) {
51+
$results[$key] = $model::validate($this, $relationData);
52+
} else {
53+
$results[$key] = [trans('validation.zip_model_expected', ['type' => gettype($relationData)])];
54+
}
55+
}
56+
57+
return $results;
58+
}
3259
}

0 commit comments

Comments
 (0)