- 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.
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';
{
$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);
$this->updateBooks($shelf, $bookIds);
}
- if (isset($input['image'])) {
+ if (array_key_exists('image', $input)) {
$this->baseRepo->updateCoverImage($shelf, $input['image'], $input['image'] === null);
}
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.
$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);
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
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;
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']);
}
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());
+ }
}
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']);
}
// 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',
]])
</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
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
</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>
--- /dev/null
+<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>
\ No newline at end of file
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']);