mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-22 11:50:27 +08:00
ZIP Exports: Started import validation
This commit is contained in:
parent
a56a28fbb7
commit
b50b7b667d
12
app/Exceptions/ZipExportValidationException.php
Normal file
12
app/Exceptions/ZipExportValidationException.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
class ZipExportValidationException extends \Exception
|
||||
{
|
||||
public function __construct(
|
||||
public array $errors,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
|
@ -21,6 +21,12 @@ class ImportController extends Controller
|
|||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'file' => ['required', 'file']
|
||||
]);
|
||||
|
||||
$file = $request->file('file');
|
||||
$file->getRealPath();
|
||||
// TODO - Read existing ZIP upload and send through validator
|
||||
// TODO - If invalid, return user with errors
|
||||
// TODO - Upload to storage
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace BookStack\Exports\ZipExports\Models;
|
||||
|
||||
use BookStack\Exports\ZipExports\ZipExportFiles;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
use BookStack\Uploads\Attachment;
|
||||
|
||||
class ZipExportAttachment extends ZipExportModel
|
||||
|
@ -35,4 +36,17 @@ class ZipExportAttachment extends ZipExportModel
|
|||
return self::fromModel($attachment, $files);
|
||||
}, $attachmentArray));
|
||||
}
|
||||
|
||||
public static function validate(ZipValidationHelper $context, array $data): array
|
||||
{
|
||||
$rules = [
|
||||
'id' => ['nullable', 'int'],
|
||||
'name' => ['required', 'string', 'min:1'],
|
||||
'order' => ['nullable', 'integer'],
|
||||
'link' => ['required_without:file', 'nullable', 'string'],
|
||||
'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()],
|
||||
];
|
||||
|
||||
return $context->validateArray($data, $rules);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace BookStack\Exports\ZipExports\Models;
|
||||
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
use JsonSerializable;
|
||||
|
||||
abstract class ZipExportModel implements JsonSerializable
|
||||
|
@ -17,4 +18,12 @@ abstract class ZipExportModel implements JsonSerializable
|
|||
$publicProps = get_object_vars(...)->__invoke($this);
|
||||
return array_filter($publicProps, fn ($value) => $value !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given array of data intended for this model.
|
||||
* Return an array of validation errors messages.
|
||||
* Child items can be considered in the validation result by returning a keyed
|
||||
* item in the array for its own validation messages.
|
||||
*/
|
||||
abstract public static function validate(ZipValidationHelper $context, array $data): array;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace BookStack\Exports\ZipExports\Models;
|
||||
|
||||
use BookStack\Activity\Models\Tag;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
|
||||
class ZipExportTag extends ZipExportModel
|
||||
{
|
||||
|
@ -24,4 +25,15 @@ class ZipExportTag extends ZipExportModel
|
|||
{
|
||||
return array_values(array_map(self::fromModel(...), $tagArray));
|
||||
}
|
||||
|
||||
public static function validate(ZipValidationHelper $context, array $data): array
|
||||
{
|
||||
$rules = [
|
||||
'name' => ['required', 'string', 'min:1'],
|
||||
'value' => ['nullable', 'string'],
|
||||
'order' => ['nullable', 'integer'],
|
||||
];
|
||||
|
||||
return $context->validateArray($data, $rules);
|
||||
}
|
||||
}
|
||||
|
|
63
app/Exports/ZipExports/ZipExportValidator.php
Normal file
63
app/Exports/ZipExports/ZipExportValidator.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use BookStack\Exceptions\ZipExportValidationException;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipExportValidator
|
||||
{
|
||||
protected array $errors = [];
|
||||
|
||||
public function __construct(
|
||||
protected string $zipPath,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipExportValidationException
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
// TODO - Return type
|
||||
// TODO - extract messages to translations?
|
||||
|
||||
// Validate file exists
|
||||
if (!file_exists($this->zipPath) || !is_readable($this->zipPath)) {
|
||||
$this->throwErrors("Could not read ZIP file");
|
||||
}
|
||||
|
||||
// Validate file is valid zip
|
||||
$zip = new \ZipArchive();
|
||||
$opened = $zip->open($this->zipPath, ZipArchive::RDONLY);
|
||||
if ($opened !== true) {
|
||||
$this->throwErrors("Could not read ZIP file");
|
||||
}
|
||||
|
||||
// Validate json data exists, including metadata
|
||||
$jsonData = $zip->getFromName('data.json') ?: '';
|
||||
$importData = json_decode($jsonData, true);
|
||||
if (!$importData) {
|
||||
$this->throwErrors("Could not decode ZIP data.json content");
|
||||
}
|
||||
|
||||
if (isset($importData['book'])) {
|
||||
// TODO - Validate book
|
||||
} else if (isset($importData['chapter'])) {
|
||||
// TODO - Validate chapter
|
||||
} else if (isset($importData['page'])) {
|
||||
// TODO - Validate page
|
||||
} else {
|
||||
$this->throwErrors("ZIP file has no book, chapter or page data");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipExportValidationException
|
||||
*/
|
||||
protected function throwErrors(...$errorsToAdd): never
|
||||
{
|
||||
array_push($this->errors, ...$errorsToAdd);
|
||||
throw new ZipExportValidationException($this->errors);
|
||||
}
|
||||
}
|
26
app/Exports/ZipExports/ZipFileReferenceRule.php
Normal file
26
app/Exports/ZipExports/ZipFileReferenceRule.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipFileReferenceRule implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
protected ZipValidationHelper $context,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (!$this->context->zipFileExists($value)) {
|
||||
$fail('validation.zip_file')->translate();
|
||||
}
|
||||
}
|
||||
}
|
32
app/Exports/ZipExports/ZipValidationHelper.php
Normal file
32
app/Exports/ZipExports/ZipValidationHelper.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use Illuminate\Validation\Factory;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipValidationHelper
|
||||
{
|
||||
protected Factory $validationFactory;
|
||||
|
||||
public function __construct(
|
||||
protected ZipArchive $zip,
|
||||
) {
|
||||
$this->validationFactory = app(Factory::class);
|
||||
}
|
||||
|
||||
public function validateArray(array $data, array $rules): array
|
||||
{
|
||||
return $this->validationFactory->make($data, $rules)->errors()->messages();
|
||||
}
|
||||
|
||||
public function zipFileExists(string $name): bool
|
||||
{
|
||||
return $this->zip->statName("files/{$name}") !== false;
|
||||
}
|
||||
|
||||
public function fileReferenceRule(): ZipFileReferenceRule
|
||||
{
|
||||
return new ZipFileReferenceRule($this);
|
||||
}
|
||||
}
|
|
@ -105,6 +105,8 @@ return [
|
|||
'url' => 'The :attribute format is invalid.',
|
||||
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
|
||||
// Custom validation lines
|
||||
'custom' => [
|
||||
'password-confirm' => [
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{{ csrf_field() }}
|
||||
<div class="flex-container-row justify-space-between wrap gap-x-xl gap-y-s">
|
||||
<p class="flex min-width-l text-muted mb-s">
|
||||
Import content using a portable zip export from the same, or a different, instance.
|
||||
Import books, chapters & pages using a portable zip export from the same, or a different, instance.
|
||||
Select a ZIP file to import then press "Validate Import" to proceed.
|
||||
After the file has been uploaded and validated you'll be able to configure & confirm the import in the next view.
|
||||
</p>
|
||||
|
|
Loading…
Reference in New Issue
Block a user