Skip to content

Commit 74fce96

Browse files
committed
ZIP Import: Added model+migration, and reader class
1 parent 259aa82 commit 74fce96

File tree

8 files changed

+234
-35
lines changed

8 files changed

+234
-35
lines changed

app/Exports/Controllers/ImportController.php

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

33
namespace BookStack\Exports\Controllers;
44

5+
use BookStack\Exports\Import;
6+
use BookStack\Exports\ZipExports\ZipExportReader;
57
use BookStack\Exports\ZipExports\ZipExportValidator;
68
use BookStack\Http\Controller;
79
use Illuminate\Http\Request;
@@ -37,17 +39,23 @@ public function upload(Request $request)
3739
return redirect('/import');
3840
}
3941

42+
$zipEntityInfo = (new ZipExportReader($zipPath))->getEntityInfo();
43+
$import = new Import();
44+
$import->name = $zipEntityInfo['name'];
45+
$import->book_count = $zipEntityInfo['book_count'];
46+
$import->chapter_count = $zipEntityInfo['chapter_count'];
47+
$import->page_count = $zipEntityInfo['page_count'];
48+
$import->created_by = user()->id;
49+
$import->size = filesize($zipPath);
50+
// TODO - Set path
51+
// TODO - Save
52+
53+
// TODO - Split out attachment service to separate out core filesystem/disk stuff
54+
// To reuse for import handling
55+
4056
dd('passed');
4157
// TODO - Upload to storage
4258
// TODO - Store info/results for display:
43-
// - zip_path
44-
// - name (From name of thing being imported)
45-
// - size
46-
// - book_count
47-
// - chapter_count
48-
// - page_count
49-
// - created_by
50-
// - created_at/updated_at
5159
// TODO - Send user to next import stage
5260
}
5361
}

app/Exports/Import.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace BookStack\Exports;
4+
5+
use Carbon\Carbon;
6+
use Illuminate\Database\Eloquent\Factories\HasFactory;
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
/**
10+
* @property string $path
11+
* @property string $name
12+
* @property int $size - ZIP size in bytes
13+
* @property int $book_count
14+
* @property int $chapter_count
15+
* @property int $page_count
16+
* @property int $created_by
17+
* @property Carbon $created_at
18+
* @property Carbon $updated_at
19+
*/
20+
class Import extends Model
21+
{
22+
use HasFactory;
23+
24+
public const TYPE_BOOK = 'book';
25+
public const TYPE_CHAPTER = 'chapter';
26+
public const TYPE_PAGE = 'page';
27+
28+
/**
29+
* Get the type (model) that this import is intended to be.
30+
*/
31+
public function getType(): string
32+
{
33+
if ($this->book_count === 1) {
34+
return self::TYPE_BOOK;
35+
} elseif ($this->chapter_count === 1) {
36+
return self::TYPE_CHAPTER;
37+
}
38+
39+
return self::TYPE_PAGE;
40+
}
41+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace BookStack\Exports\ZipExports;
4+
5+
use BookStack\Exceptions\ZipExportException;
6+
use ZipArchive;
7+
8+
class ZipExportReader
9+
{
10+
protected ZipArchive $zip;
11+
protected bool $open = false;
12+
13+
public function __construct(
14+
protected string $zipPath,
15+
) {
16+
$this->zip = new ZipArchive();
17+
}
18+
19+
/**
20+
* @throws ZipExportException
21+
*/
22+
protected function open(): void
23+
{
24+
if ($this->open) {
25+
return;
26+
}
27+
28+
// Validate file exists
29+
if (!file_exists($this->zipPath) || !is_readable($this->zipPath)) {
30+
throw new ZipExportException(trans('errors.import_zip_cant_read'));
31+
}
32+
33+
// Validate file is valid zip
34+
$opened = $this->zip->open($this->zipPath, ZipArchive::RDONLY);
35+
if ($opened !== true) {
36+
throw new ZipExportException(trans('errors.import_zip_cant_read'));
37+
}
38+
39+
$this->open = true;
40+
}
41+
42+
public function close(): void
43+
{
44+
if ($this->open) {
45+
$this->zip->close();
46+
$this->open = false;
47+
}
48+
}
49+
50+
/**
51+
* @throws ZipExportException
52+
*/
53+
public function readData(): array
54+
{
55+
$this->open();
56+
57+
// Validate json data exists, including metadata
58+
$jsonData = $this->zip->getFromName('data.json') ?: '';
59+
$importData = json_decode($jsonData, true);
60+
if (!$importData) {
61+
throw new ZipExportException(trans('errors.import_zip_cant_decode_data'));
62+
}
63+
64+
return $importData;
65+
}
66+
67+
public function fileExists(string $fileName): bool
68+
{
69+
return $this->zip->statName("files/{$fileName}") !== false;
70+
}
71+
72+
/**
73+
* @throws ZipExportException
74+
* @returns array{name: string, book_count: int, chapter_count: int, page_count: int}
75+
*/
76+
public function getEntityInfo(): array
77+
{
78+
$data = $this->readData();
79+
$info = ['name' => '', 'book_count' => 0, 'chapter_count' => 0, 'page_count' => 0];
80+
81+
if (isset($data['book'])) {
82+
$info['name'] = $data['book']['name'] ?? '';
83+
$info['book_count']++;
84+
$chapters = $data['book']['chapters'] ?? [];
85+
$pages = $data['book']['pages'] ?? [];
86+
$info['chapter_count'] += count($chapters);
87+
$info['page_count'] += count($pages);
88+
foreach ($chapters as $chapter) {
89+
$info['page_count'] += count($chapter['pages'] ?? []);
90+
}
91+
} elseif (isset($data['chapter'])) {
92+
$info['name'] = $data['chapter']['name'] ?? '';
93+
$info['chapter_count']++;
94+
$info['page_count'] += count($data['chapter']['pages'] ?? []);
95+
} elseif (isset($data['page'])) {
96+
$info['name'] = $data['page']['name'] ?? '';
97+
$info['page_count']++;
98+
}
99+
100+
return $info;
101+
}
102+
}

app/Exports/ZipExports/ZipExportValidator.php

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

33
namespace BookStack\Exports\ZipExports;
44

5+
use BookStack\Exceptions\ZipExportException;
56
use BookStack\Exports\ZipExports\Models\ZipExportBook;
67
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
78
use BookStack\Exports\ZipExports\Models\ZipExportPage;
8-
use ZipArchive;
99

1010
class ZipExportValidator
1111
{
@@ -16,26 +16,14 @@ public function __construct(
1616

1717
public function validate(): array
1818
{
19-
// Validate file exists
20-
if (!file_exists($this->zipPath) || !is_readable($this->zipPath)) {
21-
return ['format' => trans('errors.import_zip_cant_read')];
19+
$reader = new ZipExportReader($this->zipPath);
20+
try {
21+
$importData = $reader->readData();
22+
} catch (ZipExportException $exception) {
23+
return ['format' => $exception->getMessage()];
2224
}
2325

24-
// Validate file is valid zip
25-
$zip = new \ZipArchive();
26-
$opened = $zip->open($this->zipPath, ZipArchive::RDONLY);
27-
if ($opened !== true) {
28-
return ['format' => trans('errors.import_zip_cant_read')];
29-
}
30-
31-
// Validate json data exists, including metadata
32-
$jsonData = $zip->getFromName('data.json') ?: '';
33-
$importData = json_decode($jsonData, true);
34-
if (!$importData) {
35-
return ['format' => trans('errors.import_zip_cant_decode_data')];
36-
}
37-
38-
$helper = new ZipValidationHelper($zip);
26+
$helper = new ZipValidationHelper($reader);
3927

4028
if (isset($importData['book'])) {
4129
$modelErrors = ZipExportBook::validate($helper, $importData['book']);

app/Exports/ZipExports/ZipFileReferenceRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function __construct(
1919
*/
2020
public function validate(string $attribute, mixed $value, Closure $fail): void
2121
{
22-
if (!$this->context->zipFileExists($value)) {
22+
if (!$this->context->zipReader->fileExists($value)) {
2323
$fail('validation.zip_file')->translate();
2424
}
2525
}

app/Exports/ZipExports/ZipValidationHelper.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
use BookStack\Exports\ZipExports\Models\ZipExportModel;
66
use Illuminate\Validation\Factory;
7-
use ZipArchive;
87

98
class ZipValidationHelper
109
{
1110
protected Factory $validationFactory;
1211

1312
public function __construct(
14-
protected ZipArchive $zip,
13+
public ZipExportReader $zipReader,
1514
) {
1615
$this->validationFactory = app(Factory::class);
1716
}
@@ -27,11 +26,6 @@ public function validateData(array $data, array $rules): array
2726
return $messages;
2827
}
2928

30-
public function zipFileExists(string $name): bool
31-
{
32-
return $this->zip->statName("files/{$name}") !== false;
33-
}
34-
3529
public function fileReferenceRule(): ZipFileReferenceRule
3630
{
3731
return new ZipFileReferenceRule($this);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Database\Factories\Exports;
4+
5+
use BookStack\Users\Models\User;
6+
use Illuminate\Database\Eloquent\Factories\Factory;
7+
use Illuminate\Support\Str;
8+
9+
class ImportFactory extends Factory
10+
{
11+
/**
12+
* The name of the factory's corresponding model.
13+
*
14+
* @var string
15+
*/
16+
protected $model = \BookStack\Exports\Import::class;
17+
18+
/**
19+
* Define the model's default state.
20+
*/
21+
public function definition(): array
22+
{
23+
return [
24+
'path' => 'uploads/imports/' . Str::random(10) . '.zip',
25+
'name' => $this->faker->words(3, true),
26+
'book_count' => 1,
27+
'chapter_count' => 5,
28+
'page_count' => 15,
29+
'created_at' => User::factory(),
30+
];
31+
}
32+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('imports', function (Blueprint $table) {
15+
$table->increments('id');
16+
$table->string('name');
17+
$table->string('path');
18+
$table->integer('size');
19+
$table->integer('book_count');
20+
$table->integer('chapter_count');
21+
$table->integer('page_count');
22+
$table->integer('created_by');
23+
$table->timestamps();
24+
});
25+
}
26+
27+
/**
28+
* Reverse the migrations.
29+
*/
30+
public function down(): void
31+
{
32+
Schema::dropIfExists('imports');
33+
}
34+
};

0 commit comments

Comments
 (0)