Skip to content

Commit c6109c7

Browse files
committed
ZIP Imports: Added listing, show view, delete, activity
1 parent 8ea3855 commit c6109c7

File tree

11 files changed

+193
-6
lines changed

11 files changed

+193
-6
lines changed

app/Activity/ActivityType.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class ActivityType
6767
const WEBHOOK_UPDATE = 'webhook_update';
6868
const WEBHOOK_DELETE = 'webhook_delete';
6969

70+
const IMPORT_CREATE = 'import_create';
71+
const IMPORT_RUN = 'import_run';
72+
const IMPORT_DELETE = 'import_delete';
73+
7074
/**
7175
* Get all the possible values.
7276
*/

app/Exports/Controllers/ImportController.php

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace BookStack\Exports\Controllers;
46

7+
use BookStack\Activity\ActivityType;
58
use BookStack\Exceptions\ZipValidationException;
69
use BookStack\Exports\ImportRepo;
710
use BookStack\Http\Controller;
@@ -16,15 +19,26 @@ public function __construct(
1619
$this->middleware('can:content-import');
1720
}
1821

22+
/**
23+
* Show the view to start a new import, and also list out the existing
24+
* in progress imports that are visible to the user.
25+
*/
1926
public function start(Request $request)
2027
{
21-
// TODO - Show existing imports for user (or for all users if admin-level user)
28+
// TODO - Test visibility access for listed items
29+
$imports = $this->imports->getVisibleImports();
30+
31+
$this->setPageTitle(trans('entities.import'));
2232

2333
return view('exports.import', [
34+
'imports' => $imports,
2435
'zipErrors' => session()->pull('validation_errors') ?? [],
2536
]);
2637
}
2738

39+
/**
40+
* Upload, validate and store an import file.
41+
*/
2842
public function upload(Request $request)
2943
{
3044
$this->validate($request, [
@@ -39,6 +53,38 @@ public function upload(Request $request)
3953
return redirect('/import');
4054
}
4155

42-
return redirect("imports/{$import->id}");
56+
$this->logActivity(ActivityType::IMPORT_CREATE, $import);
57+
58+
return redirect($import->getUrl());
59+
}
60+
61+
/**
62+
* Show a pending import, with a form to allow progressing
63+
* with the import process.
64+
*/
65+
public function show(int $id)
66+
{
67+
// TODO - Test visibility access
68+
$import = $this->imports->findVisible($id);
69+
70+
$this->setPageTitle(trans('entities.import_continue'));
71+
72+
return view('exports.import-show', [
73+
'import' => $import,
74+
]);
75+
}
76+
77+
/**
78+
* Delete an active pending import from the filesystem and database.
79+
*/
80+
public function delete(int $id)
81+
{
82+
// TODO - Test visibility access
83+
$import = $this->imports->findVisible($id);
84+
$this->imports->deleteImport($import);
85+
86+
$this->logActivity(ActivityType::IMPORT_DELETE, $import);
87+
88+
return redirect('/import');
4389
}
4490
}

app/Exports/Import.php

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

33
namespace BookStack\Exports;
44

5+
use BookStack\Activity\Models\Loggable;
56
use Carbon\Carbon;
67
use Illuminate\Database\Eloquent\Factories\HasFactory;
78
use Illuminate\Database\Eloquent\Model;
@@ -17,7 +18,7 @@
1718
* @property Carbon $created_at
1819
* @property Carbon $updated_at
1920
*/
20-
class Import extends Model
21+
class Import extends Model implements Loggable
2122
{
2223
use HasFactory;
2324

@@ -38,4 +39,24 @@ public function getType(): string
3839

3940
return self::TYPE_PAGE;
4041
}
42+
43+
public function getSizeString(): string
44+
{
45+
$mb = round($this->size / 1000000, 2);
46+
return "{$mb} MB";
47+
}
48+
49+
/**
50+
* Get the URL to view/continue this import.
51+
*/
52+
public function getUrl(string $path = ''): string
53+
{
54+
$path = ltrim($path, '/');
55+
return url("/import/{$this->id}" . ($path ? '/' . $path : ''));
56+
}
57+
58+
public function logDescriptor(): string
59+
{
60+
return "({$this->id}) {$this->name}";
61+
}
4162
}

app/Exports/ImportRepo.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use BookStack\Exports\ZipExports\ZipExportReader;
77
use BookStack\Exports\ZipExports\ZipExportValidator;
88
use BookStack\Uploads\FileStorage;
9+
use Illuminate\Database\Eloquent\Collection;
910
use Symfony\Component\HttpFoundation\File\UploadedFile;
1011

1112
class ImportRepo
@@ -15,6 +16,31 @@ public function __construct(
1516
) {
1617
}
1718

19+
/**
20+
* @return Collection<Import>
21+
*/
22+
public function getVisibleImports(): Collection
23+
{
24+
$query = Import::query();
25+
26+
if (!userCan('settings-manage')) {
27+
$query->where('created_by', user()->id);
28+
}
29+
30+
return $query->get();
31+
}
32+
33+
public function findVisible(int $id): Import
34+
{
35+
$query = Import::query();
36+
37+
if (!userCan('settings-manage')) {
38+
$query->where('created_by', user()->id);
39+
}
40+
41+
return $query->findOrFail($id);
42+
}
43+
1844
public function storeFromUpload(UploadedFile $file): Import
1945
{
2046
$zipPath = $file->getRealPath();
@@ -45,4 +71,10 @@ public function storeFromUpload(UploadedFile $file): Import
4571

4672
return $import;
4773
}
74+
75+
public function deleteImport(Import $import): void
76+
{
77+
$this->storage->delete($import->path);
78+
$import->delete();
79+
}
4880
}

app/Http/Controller.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,8 @@ protected function showErrorNotification(string $message): void
152152

153153
/**
154154
* Log an activity in the system.
155-
*
156-
* @param string|Loggable $detail
157155
*/
158-
protected function logActivity(string $type, $detail = ''): void
156+
protected function logActivity(string $type, string|Loggable $detail = ''): void
159157
{
160158
Activity::add($type, $detail);
161159
}

lang/en/activities.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@
8484
'webhook_delete' => 'deleted webhook',
8585
'webhook_delete_notification' => 'Webhook successfully deleted',
8686

87+
// Imports
88+
'import_create' => 'created import',
89+
'import_create_notification' => 'Import successfully uploaded',
90+
'import_run' => 'updated import',
91+
'import_run_notification' => 'Content successfully imported',
92+
'import_delete' => 'deleted import',
93+
'import_delete_notification' => 'Import successfully deleted',
94+
8795
// Users
8896
'user_create' => 'created user',
8997
'user_create_notification' => 'User successfully created',

lang/en/entities.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
'import_desc' => '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.',
4949
'import_zip_select' => 'Select ZIP file to upload',
5050
'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:',
51+
'import_pending' => 'Pending Imports',
52+
'import_pending_none' => 'No imports have been started.',
53+
'import_continue' => 'Continue Import',
54+
'import_run' => 'Run Import',
55+
'import_delete_confirm' => 'Are you sure you want to delete this import?',
56+
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
5157

5258
// Permissions and restrictions
5359
'permissions' => 'Permissions',
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
@extends('layouts.simple')
2+
3+
@section('body')
4+
5+
<div class="container small">
6+
7+
<main class="card content-wrap auto-height mt-xxl">
8+
<h1 class="list-heading">{{ trans('entities.import_continue') }}</h1>
9+
<form action="{{ url('/import') }}" enctype="multipart/form-data" method="POST">
10+
{{ csrf_field() }}
11+
</form>
12+
13+
<div class="text-right">
14+
<a href="{{ url('/import') }}" class="button outline">{{ trans('common.cancel') }}</a>
15+
<div component="dropdown" class="inline block mx-s">
16+
<button refs="dropdown@toggle"
17+
type="button"
18+
title="{{ trans('common.delete') }}"
19+
class="button outline">{{ trans('common.delete') }}</button>
20+
<div refs="dropdown@menu" class="dropdown-menu">
21+
<p class="text-neg bold small px-m mb-xs">{{ trans('entities.import_delete_confirm') }}</p>
22+
<p class="small px-m mb-xs">{{ trans('entities.import_delete_desc') }}</p>
23+
<button type="submit" form="import-delete-form" class="text-link small text-item">{{ trans('common.confirm') }}</button>
24+
</div>
25+
</div>
26+
<button type="submit" class="button">{{ trans('entities.import_run') }}</button>
27+
</div>
28+
</main>
29+
</div>
30+
31+
<form id="import-delete-form"
32+
action="{{ $import->getUrl() }}"
33+
method="post">
34+
{{ method_field('DELETE') }}
35+
{{ csrf_field() }}
36+
</form>
37+
38+
@stop

resources/views/exports/import.blade.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ class="custom-simple-file-input">
3838
</div>
3939
</form>
4040
</main>
41+
42+
<main class="card content-wrap auto-height mt-xxl">
43+
<h2 class="list-heading">{{ trans('entities.import_pending') }}</h2>
44+
@if(count($imports) === 0)
45+
<p>{{ trans('entities.import_pending_none') }}</p>
46+
@else
47+
<div class="item-list my-m">
48+
@foreach($imports as $import)
49+
@include('exports.parts.import', ['import' => $import])
50+
@endforeach
51+
</div>
52+
@endif
53+
</main>
4154
</div>
4255

4356
@stop
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@php
2+
$type = $import->getType();
3+
@endphp
4+
<div class="item-list-row flex-container-row items-center justify-space-between wrap">
5+
<div class="px-m py-s">
6+
<a href="{{ $import->getUrl() }}"
7+
class="text-{{ $type }}">@icon($type) {{ $import->name }}</a>
8+
</div>
9+
<div class="px-m py-s flex-container-row gap-m items-center">
10+
@if($type === 'book')
11+
<div class="text-chapter opacity-80 bold">@icon('chapter') {{ $import->chapter_count }}</div>
12+
@endif
13+
@if($type === 'book' || $type === 'chapter')
14+
<div class="text-page opacity-80 bold">@icon('page') {{ $import->page_count }}</div>
15+
@endif
16+
<div class="bold opacity-80">{{ $import->getSizeString() }}</div>
17+
<div class="bold opacity-80 text-muted" title="{{ $import->created_at->toISOString() }}">@icon('time'){{ $import->created_at->diffForHumans() }}</div>
18+
</div>
19+
</div>

0 commit comments

Comments
 (0)