]> BookStack Code Mirror - bookstack/blob - app/Exports/ImportRepo.php
ZIP Imports: Added API examples, finished testing
[bookstack] / app / Exports / ImportRepo.php
1 <?php
2
3 namespace BookStack\Exports;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Entities\Models\Entity;
7 use BookStack\Entities\Queries\EntityQueries;
8 use BookStack\Exceptions\FileUploadException;
9 use BookStack\Exceptions\ZipExportException;
10 use BookStack\Exceptions\ZipImportException;
11 use BookStack\Exceptions\ZipValidationException;
12 use BookStack\Exports\ZipExports\Models\ZipExportBook;
13 use BookStack\Exports\ZipExports\Models\ZipExportChapter;
14 use BookStack\Exports\ZipExports\Models\ZipExportPage;
15 use BookStack\Exports\ZipExports\ZipExportReader;
16 use BookStack\Exports\ZipExports\ZipExportValidator;
17 use BookStack\Exports\ZipExports\ZipImportRunner;
18 use BookStack\Facades\Activity;
19 use BookStack\Uploads\FileStorage;
20 use Illuminate\Database\Eloquent\Builder;
21 use Illuminate\Database\Eloquent\Collection;
22 use Illuminate\Support\Facades\DB;
23 use Symfony\Component\HttpFoundation\File\UploadedFile;
24
25 class ImportRepo
26 {
27     public function __construct(
28         protected FileStorage $storage,
29         protected ZipImportRunner $importer,
30         protected EntityQueries $entityQueries,
31     ) {
32     }
33
34     /**
35      * @return Collection<Import>
36      */
37     public function getVisibleImports(): Collection
38     {
39         return $this->queryVisible()->get();
40     }
41
42     public function queryVisible(): Builder
43     {
44         $query = Import::query();
45
46         if (!userCan('settings-manage')) {
47             $query->where('created_by', user()->id);
48         }
49
50         return $query;
51     }
52
53     public function findVisible(int $id): Import
54     {
55         $query = Import::query();
56
57         if (!userCan('settings-manage')) {
58             $query->where('created_by', user()->id);
59         }
60
61         return $query->findOrFail($id);
62     }
63
64     /**
65      * @throws FileUploadException
66      * @throws ZipValidationException
67      * @throws ZipExportException
68      */
69     public function storeFromUpload(UploadedFile $file): Import
70     {
71         $zipPath = $file->getRealPath();
72         $reader = new ZipExportReader($zipPath);
73
74         $errors = (new ZipExportValidator($reader))->validate();
75         if ($errors) {
76             throw new ZipValidationException($errors);
77         }
78
79         $exportModel = $reader->decodeDataToExportModel();
80
81         $import = new Import();
82         $import->type = match (get_class($exportModel)) {
83             ZipExportPage::class => 'page',
84             ZipExportChapter::class => 'chapter',
85             ZipExportBook::class => 'book',
86         };
87
88         $import->name = $exportModel->name;
89         $import->created_by = user()->id;
90         $import->size = filesize($zipPath);
91
92         $exportModel->metadataOnly();
93         $import->metadata = json_encode($exportModel);
94
95         $path = $this->storage->uploadFile(
96             $file,
97             'uploads/files/imports/',
98             '',
99             'zip'
100         );
101
102         $import->path = $path;
103         $import->save();
104
105         Activity::add(ActivityType::IMPORT_CREATE, $import);
106
107         return $import;
108     }
109
110     /**
111      * @throws ZipImportException
112      */
113     public function runImport(Import $import, ?string $parent = null): Entity
114     {
115         $parentModel = null;
116         if ($import->type === 'page' || $import->type === 'chapter') {
117             $parentModel = $parent ? $this->entityQueries->findVisibleByStringIdentifier($parent) : null;
118         }
119
120         DB::beginTransaction();
121         try {
122             $model = $this->importer->run($import, $parentModel);
123         } catch (ZipImportException $e) {
124             DB::rollBack();
125             $this->importer->revertStoredFiles();
126             throw $e;
127         }
128
129         DB::commit();
130         $this->deleteImport($import);
131         Activity::add(ActivityType::IMPORT_RUN, $import);
132
133         return $model;
134     }
135
136     public function deleteImport(Import $import): void
137     {
138         $this->storage->delete($import->path);
139         $import->delete();
140
141         Activity::add(ActivityType::IMPORT_DELETE, $import);
142     }
143 }