Review of #2682, Also added parent deletion link on restore

On restore, added a link to the parent deletion restore if any exists
on a cascading parent. Added a test to cover this case to ensure its shown.

Also tweaked default empty state message on recycle bin item list to align
with new column count.

Also done a little existing code cleanup including a getUrl helper on
the deletion items.

Related to #2682 & #2594
This commit is contained in:
Dan Brown 2021-06-26 12:12:11 +01:00
parent 8a9505bf8c
commit 3a402f6adc
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
7 changed files with 63 additions and 9 deletions

View File

@ -7,6 +7,9 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property Model deletable
*/
class Deletion extends Model implements Loggable
{
@ -45,4 +48,12 @@ class Deletion extends Model implements Loggable
$deletable = $this->deletable()->first();
return "Deletion ({$this->id}) for {$deletable->getType()} ({$deletable->id}) {$deletable->name}";
}
/**
* Get a URL for this specific deletion.
*/
public function getUrl($path): string
{
return url("/settings/recycle-bin/{$this->id}/" . ltrim($path, '/'));
}
}

View File

@ -266,10 +266,10 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function getParent(): ?Entity
{
if ($this->isA('page')) {
if ($this instanceof Page) {
return $this->chapter_id ? $this->chapter()->withTrashed()->first() : $this->book()->withTrashed()->first();
}
if ($this->isA('chapter')) {
if ($this instanceof Chapter) {
return $this->book()->withTrashed()->first();
}
return null;

View File

@ -2,6 +2,7 @@
use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Tools\TrashCan;
class RecycleBinController extends Controller
@ -44,8 +45,23 @@ class RecycleBinController extends Controller
/** @var Deletion $deletion */
$deletion = Deletion::query()->findOrFail($id);
// Walk the parent chain to find any cascading parent deletions
$currentDeletable = $deletion->deletable;
$searching = true;
while ($searching && $currentDeletable instanceof Entity) {
$parent = $currentDeletable->getParent();
if ($parent && $parent->trashed()) {
$currentDeletable = $parent;
} else {
$searching = false;
}
}
/** @var ?Deletion $parentDeletion */
$parentDeletion = ($currentDeletable === $deletion->deletable) ? null : $currentDeletable->deletions()->first();
return view('settings.recycle-bin.restore', [
'deletion' => $deletion,
'parentDeletion' => $parentDeletion,
]);
}

View File

@ -105,6 +105,7 @@ return [
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',

View File

@ -47,7 +47,7 @@
</tr>
@if(count($deletions) === 0)
<tr>
<td colspan="4">
<td colspan="5">
<p class="text-muted"><em>{{ trans('settings.recycle_bin_contents_empty') }}</em></p>
</td>
</tr>
@ -95,8 +95,8 @@
<div component="dropdown" class="dropdown-container">
<button type="button" refs="dropdown@toggle" class="button outline">{{ trans('common.actions') }}</button>
<ul refs="dropdown@menu" class="dropdown-menu">
<li><a class="block" href="{{ url('/settings/recycle-bin/'.$deletion->id.'/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
<li><a class="block" href="{{ url('/settings/recycle-bin/'.$deletion->id.'/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
<li><a class="block" href="{{ $deletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
<li><a class="block" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
</ul>
</div>
</td>

View File

@ -10,7 +10,7 @@
<div class="card content-wrap auto-height">
<h2 class="list-heading">{{ trans('settings.recycle_bin_restore') }}</h2>
<p class="text-muted">{{ trans('settings.recycle_bin_restore_confirm') }}</p>
<form action="{{ url('/settings/recycle-bin/' . $deletion->id . '/restore') }}" method="post">
<form action="{{ $deletion->getUrl('/restore') }}" method="post">
{!! csrf_field() !!}
<a href="{{ url('/settings/recycle-bin') }}" class="button outline">{{ trans('common.cancel') }}</a>
<button type="submit" class="button">{{ trans('settings.recycle_bin_restore') }}</button>
@ -19,9 +19,17 @@
@if($deletion->deletable instanceof \BookStack\Entities\Models\Entity)
<hr class="mt-m">
<h5>{{ trans('settings.recycle_bin_restore_list') }}</h5>
@if($deletion->deletable->getParent() && $deletion->deletable->getParent()->trashed())
<p class="text-neg">{{ trans('settings.recycle_bin_restore_deleted_parent') }}</p>
@endif
<div class="flex-container-row mb-s items-center">
@if($deletion->deletable->getParent() && $deletion->deletable->getParent()->trashed())
<div class="text-neg flex">{{ trans('settings.recycle_bin_restore_deleted_parent') }}</div>
@endif
@if($parentDeletion)
<div class="flex fit-content ml-m">
<a class="button outline" href="{{ $parentDeletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore_parent') }}</a>
</div>
@endif
</div>
@include('settings.recycle-bin.deletable-entity-list', ['entity' => $deletion->deletable])
@endif

View File

@ -247,4 +247,22 @@ class RecycleBinTest extends TestCase
$chapter->refresh();
$this->assertNull($chapter->deleted_at);
}
public function test_restore_page_shows_link_to_parent_restore_if_parent_also_deleted()
{
/** @var Book $book */
$book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
$chapter = $book->chapters->first();
/** @var Page $page */
$page = $chapter->pages->first();
$this->asEditor()->delete($page->getUrl());
$this->asEditor()->delete($book->getUrl());
$bookDeletion = $book->deletions()->first();
$pageDeletion = $page->deletions()->first();
$pageRestoreView = $this->asAdmin()->get("/settings/recycle-bin/{$pageDeletion->id}/restore");
$pageRestoreView->assertSee('The parent of this item has also been deleted.');
$pageRestoreView->assertElementContains('a[href$="/settings/recycle-bin/' . $bookDeletion->id. '/restore"]', 'Restore Parent');
}
}