diff --git a/app/Actions/ActivityType.php b/app/Actions/ActivityType.php index 997cc041a..0ad25a5ab 100644 --- a/app/Actions/ActivityType.php +++ b/app/Actions/ActivityType.php @@ -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'; diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php index 03e7804d5..f37db1f06 100644 --- a/app/Entities/Repos/BookshelfRepo.php +++ b/app/Entities/Repos/BookshelfRepo.php @@ -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); } diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index c106d2fd3..e3c6bd17a 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -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. diff --git a/app/Entities/Tools/HierarchyTransformer.php b/app/Entities/Tools/HierarchyTransformer.php index 7304962b3..93c5bb9bb 100644 --- a/app/Entities/Tools/HierarchyTransformer.php +++ b/app/Entities/Tools/HierarchyTransformer.php @@ -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; } } \ No newline at end of file diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index b9dd0e799..937f7d28f 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -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()); + } } diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index ce2e508c8..2f966beed 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -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']); } diff --git a/resources/lang/en/activities.php b/resources/lang/en/activities.php index 0c3d2e704..edddf9aeb 100644 --- a/resources/lang/en/activities.php +++ b/resources/lang/en/activities.php @@ -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', diff --git a/resources/views/books/edit.blade.php b/resources/views/books/edit.blade.php index 403977121..180500e0a 100644 --- a/resources/views/books/edit.blade.php +++ b/resources/views/books/edit.blade.php @@ -14,12 +14,17 @@ ]]) -
+

{{ trans('entities.books_edit') }}

@include('books.parts.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
+ + + @if(userCan('book-delete', $book) && userCan('book-create-all') && userCan('bookshelf-create-all')) + @include('books.parts.convert-to-shelf', ['book' => $book]) + @endif @stop \ No newline at end of file diff --git a/resources/views/books/parts/convert-to-shelf.blade.php b/resources/views/books/parts/convert-to-shelf.blade.php new file mode 100644 index 000000000..700377a19 --- /dev/null +++ b/resources/views/books/parts/convert-to-shelf.blade.php @@ -0,0 +1,35 @@ +
+

Convert to Shelf

+

+ 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. + +

+ + 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. +

+
+ +
+
\ No newline at end of file diff --git a/resources/views/chapters/edit.blade.php b/resources/views/chapters/edit.blade.php index f6fd3cc6b..36058eff8 100644 --- a/resources/views/chapters/edit.blade.php +++ b/resources/views/chapters/edit.blade.php @@ -23,35 +23,9 @@
-{{-- TODO - Permissions--}} -
-

Convert to Book

-
-

- 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. -

-
- -
-
-
+ @if(userCan('chapter-delete', $chapter) && userCan('book-create-all')) + @include('chapters.parts.convert-to-book') + @endif diff --git a/resources/views/chapters/parts/convert-to-book.blade.php b/resources/views/chapters/parts/convert-to-book.blade.php new file mode 100644 index 000000000..8a5d2a181 --- /dev/null +++ b/resources/views/chapters/parts/convert-to-book.blade.php @@ -0,0 +1,28 @@ +
+

Convert to Book

+
+

+ 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. +

+
+ +
+
+
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index dfda97253..5e16e5333 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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']);