Got book to shelf conversions working

- Also extracted shelf to book view elements to own partial.
- Fixed some existing logic including image param handling in update
  request and activity logging against correct element.
This commit is contained in:
Dan Brown 2022-06-15 15:05:08 +01:00
parent 8da856bac3
commit 8c67011a1d
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
12 changed files with 110 additions and 57 deletions

View File

@ -22,6 +22,7 @@ class ActivityType
const BOOK_SORT = 'book_sort';
const BOOKSHELF_CREATE = 'bookshelf_create';
const BOOKSHELF_CREATE_FROM_BOOK = 'bookshelf_create_from_book';
const BOOKSHELF_UPDATE = 'bookshelf_update';
const BOOKSHELF_DELETE = 'bookshelf_delete';

View File

@ -89,7 +89,7 @@ class BookshelfRepo
{
$shelf = new Bookshelf();
$this->baseRepo->create($shelf, $input);
$this->baseRepo->updateCoverImage($shelf, $input['image']);
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
$this->updateBooks($shelf, $bookIds);
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
@ -107,7 +107,7 @@ class BookshelfRepo
$this->updateBooks($shelf, $bookIds);
}
if (isset($input['image'])) {
if (array_key_exists('image', $input)) {
$this->baseRepo->updateCoverImage($shelf, $input['image'], $input['image'] === null);
}

View File

@ -392,23 +392,6 @@ class PageRepo
return $parentClass::visible()->where('id', '=', $entityId)->first();
}
/**
* Change the page's parent to the given entity.
*/
protected function changeParent(Page $page, Entity $parent)
{
$book = ($parent instanceof Chapter) ? $parent->book : $parent;
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0;
$page->save();
if ($page->book->id !== $book->id) {
$page->changeBook($book->id);
}
$page->load('book');
$book->rebuildPermissions();
}
/**
* Get a page revision to update for the given page.
* Checks for an existing revisions before providing a fresh one.

View File

@ -44,14 +44,16 @@ class HierarchyTransformer
$this->trashCan->destroyEntity($chapter);
Activity::add(ActivityType::BOOK_CREATE_FROM_CHAPTER);
Activity::add(ActivityType::BOOK_CREATE_FROM_CHAPTER, $book);
return $book;
}
/**
* Transform a book into a shelf.
* Does not check permissions, check before calling.
*/
public function transformBookToShelf(Book $book): Bookshelf
{
// TODO - Check permissions before call
// Permissions: edit-book, delete-book, create-shelf
$inputData = $this->cloner->entityToInputData($book);
$shelf = $this->shelfRepo->create($inputData, []);
$this->cloner->copyEntityPermissions($book, $shelf);
@ -62,17 +64,22 @@ class HierarchyTransformer
foreach ($book->chapters as $index => $chapter) {
$newBook = $this->transformChapterToBook($chapter);
$shelfBookSyncData[$newBook->id] = ['order' => $index];
if (!$newBook->restricted) {
$this->cloner->copyEntityPermissions($shelf, $newBook);
}
}
$shelf->books()->sync($shelfBookSyncData);
if ($book->directPages->count() > 0) {
$book->name .= ' ' . trans('entities.pages');
$shelfBookSyncData[$book->id] = ['order' => count($shelfBookSyncData) + 1];
$book->save();
} else {
$this->trashCan->destroyEntity($book);
}
// TODO - Log activity for change
$shelf->books()->sync($shelfBookSyncData);
Activity::add(ActivityType::BOOKSHELF_CREATE_FROM_BOOK, $shelf);
return $shelf;
}
}

View File

@ -9,6 +9,7 @@ use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\Cloner;
use BookStack\Entities\Tools\HierarchyTransformer;
use BookStack\Entities\Tools\PermissionsUpdater;
use BookStack\Entities\Tools\ShelfContext;
use BookStack\Exceptions\ImageUploadException;
@ -166,7 +167,7 @@ class BookController extends Controller
if ($request->has('image_reset')) {
$validated['image'] = null;
} else if (is_null($validated['image'])) {
} else if (array_key_exists('image', $validated) && is_null($validated['image'])) {
unset($validated['image']);
}
@ -266,4 +267,20 @@ class BookController extends Controller
return redirect($bookCopy->getUrl());
}
/**
* Convert the chapter to a book.
*/
public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$this->checkOwnablePermission('book-update', $book);
$this->checkOwnablePermission('book-delete', $book);
$this->checkPermission('bookshelf-create-all');
$this->checkPermission('book-create-all');
$shelf = $transformer->transformBookToShelf($book);
return redirect($shelf->getUrl());
}
}

View File

@ -167,7 +167,7 @@ class BookshelfController extends Controller
if ($request->has('image_reset')) {
$validated['image'] = null;
} else if (is_null($validated['image'])) {
} else if (array_key_exists('image', $validated) && is_null($validated['image'])) {
unset($validated['image']);
}

View File

@ -40,6 +40,8 @@ return [
// Bookshelves
'bookshelf_create' => 'created bookshelf',
'bookshelf_create_notification' => 'Bookshelf successfully created',
'bookshelf_create_from_book' => 'converted book to bookshelf',
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
'bookshelf_update' => 'updated bookshelf',
'bookshelf_update_notification' => 'Bookshelf successfully updated',
'bookshelf_delete' => 'deleted bookshelf',

View File

@ -14,12 +14,17 @@
]])
</div>
<main class="content-wrap card">
<main class="content-wrap card auto-height">
<h1 class="list-heading">{{ trans('entities.books_edit') }}</h1>
<form action="{{ $book->getUrl() }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="_method" value="PUT">
@include('books.parts.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
</form>
</main>
@if(userCan('book-delete', $book) && userCan('book-create-all') && userCan('bookshelf-create-all'))
@include('books.parts.convert-to-shelf', ['book' => $book])
@endif
</div>
@stop

View File

@ -0,0 +1,35 @@
<div class="content-wrap card auto-height">
<h2 class="list-heading">Convert to Shelf</h2>
<p>
You can convert this book to a new shelf with the same contents.
Chapters contained within this book will be converted to new books.
If this book contains any pages, that are not in a chapter, this book will be renamed
and contain such pages, and this book will become part of the new shelf.
<br><br>
Any permissions set on this book will be copied to the new shelf and to all new child books
that don't have their own permissions enforced.
Note that permissions on shelves do not auto-cascade to content within, as they do for books.
</p>
<div class="text-right">
<div component="dropdown" class="dropdown-container">
<button refs="dropdown@toggle" class="button outline" aria-haspopup="true" aria-expanded="false">Convert Book</button>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s text-small text-muted">
Are you sure you want to convert this book?
<br>
This cannot be as easily undone.
</li>
<li>
<form action="{{ $book->getUrl('/convert-to-shelf') }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="text-primary text-item">{{ trans('common.confirm') }}</button>
</form>
</li>
</ul>
</div>
</div>
</div>

View File

@ -23,35 +23,9 @@
</form>
</main>
{{-- TODO - Permissions--}}
<div class="content-wrap card auto-height">
<h2 class="list-heading">Convert to Book</h2>
<div class="grid half left-focus no-row-gap">
<p>
You can convert this chapter to a new book with the same contents.
Any permissions set on this chapter will be copied to the new book but any inherited permissions,
from the parent book, will not be copied which could lead to a change of access control.
</p>
<div class="text-m-right">
<div component="dropdown" class="dropdown-container">
<button refs="dropdown@toggle" class="button outline" aria-haspopup="true" aria-expanded="false">Convert Chapter</button>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s text-small text-muted">
Are you sure you want to convert this chapter?
<br>
This cannot be as easily undone.
</li>
<li>
<form action="{{ $chapter->getUrl('/convert-to-book') }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="text-primary text-item">{{ trans('common.confirm') }}</button>
</form>
</li>
</ul>
</div>
</div>
</div>
</div>
@if(userCan('chapter-delete', $chapter) && userCan('book-create-all'))
@include('chapters.parts.convert-to-book')
@endif
</div>

View File

@ -0,0 +1,28 @@
<div class="content-wrap card auto-height">
<h2 class="list-heading">Convert to Book</h2>
<div class="grid half left-focus no-row-gap">
<p>
You can convert this chapter to a new book with the same contents.
Any permissions set on this chapter will be copied to the new book but any inherited permissions,
from the parent book, will not be copied which could lead to a change of access control.
</p>
<div class="text-m-right">
<div component="dropdown" class="dropdown-container">
<button refs="dropdown@toggle" class="button outline" aria-haspopup="true" aria-expanded="false">Convert Chapter</button>
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
<li class="px-m py-s text-small text-muted">
Are you sure you want to convert this chapter?
<br>
This cannot be as easily undone.
</li>
<li>
<form action="{{ $chapter->getUrl('/convert-to-book') }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="text-primary text-item">{{ trans('common.confirm') }}</button>
</form>
</li>
</ul>
</div>
</div>
</div>
</div>

View File

@ -82,6 +82,7 @@ Route::middleware('auth')->group(function () {
Route::get('/books/{slug}/delete', [BookController::class, 'showDelete']);
Route::get('/books/{bookSlug}/copy', [BookController::class, 'showCopy']);
Route::post('/books/{bookSlug}/copy', [BookController::class, 'copy']);
Route::post('/books/{bookSlug}/convert-to-shelf', [BookController::class, 'convertToShelf']);
Route::get('/books/{bookSlug}/sort', [BookSortController::class, 'show']);
Route::put('/books/{bookSlug}/sort', [BookSortController::class, 'update']);
Route::get('/books/{bookSlug}/export/html', [BookExportController::class, 'html']);