- Updates book/shelf cover image handling for easier cloning/handling.
- Adds core logic for promoting books/chapters up a level.
- Enables usage of book/shelf cover image via API.
Related to #1087
{
$book = new Book();
$this->baseRepo->create($book, $input);
+ $this->baseRepo->updateCoverImage($book, $input['image']);
Activity::add(ActivityType::BOOK_CREATE, $book);
return $book;
public function update(Book $book, array $input): Book
{
$this->baseRepo->update($book, $input);
+
+ if (isset($input['image'])) {
+ $this->baseRepo->updateCoverImage($book, $input['image'], $input['image'] === null);
+ }
+
Activity::add(ActivityType::BOOK_UPDATE, $book);
return $book;
{
$shelf = new Bookshelf();
$this->baseRepo->create($shelf, $input);
+ $this->baseRepo->updateCoverImage($shelf, $input['image']);
$this->updateBooks($shelf, $bookIds);
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
$this->updateBooks($shelf, $bookIds);
}
+ if (isset($input['image'])) {
+ $this->baseRepo->updateCoverImage($shelf, $input['image'], $input['image'] === null);
+ }
+
Activity::add(ActivityType::BOOKSHELF_UPDATE, $shelf);
return $shelf;
}
/**
- * Update which books are assigned to this shelf by
- * syncing the given book ids.
+ * Update which books are assigned to this shelf by syncing the given book ids.
* Function ensures the books are visible to the current user and existing.
*/
protected function updateBooks(Bookshelf $shelf, array $bookIds)
$shelf->books()->sync($syncData);
}
- /**
- * Update the given shelf cover image, or clear it.
- *
- * @throws ImageUploadException
- * @throws Exception
- */
- public function updateCoverImage(Bookshelf $shelf, ?UploadedFile $coverImage, bool $removeImage = false)
- {
- $this->baseRepo->updateCoverImage($shelf, $coverImage, $removeImage);
- }
-
/**
* Copy down the permissions of the given shelf to all child books.
*/
public function clonePage(Page $original, Entity $parent, string $newName): Page
{
$copyPage = $this->pageRepo->getNewDraftPage($parent);
- $pageData = $original->getAttributes();
-
- // Update name & tags
+ $pageData = $this->entityToInputData($original);
$pageData['name'] = $newName;
- $pageData['tags'] = $this->entityTagsToInputArray($original);
return $this->pageRepo->publishDraft($copyPage, $pageData);
}
*/
public function cloneChapter(Chapter $original, Book $parent, string $newName): Chapter
{
- $chapterDetails = $original->getAttributes();
+ $chapterDetails = $this->entityToInputData($original);
$chapterDetails['name'] = $newName;
- $chapterDetails['tags'] = $this->entityTagsToInputArray($original);
$copyChapter = $this->chapterRepo->create($chapterDetails, $parent);
*/
public function cloneBook(Book $original, string $newName): Book
{
- $bookDetails = $original->getAttributes();
+ $bookDetails = $this->entityToInputData($original);
$bookDetails['name'] = $newName;
- $bookDetails['tags'] = $this->entityTagsToInputArray($original);
$copyBook = $this->bookRepo->create($bookDetails);
}
}
- if ($original->cover) {
- try {
- $tmpImgFile = tmpfile();
- $uploadedFile = $this->imageToUploadedFile($original->cover, $tmpImgFile);
- $this->bookRepo->updateCoverImage($copyBook, $uploadedFile, false);
- } catch (\Exception $exception) {
- }
+ return $copyBook;
+ }
+
+ /**
+ * Convert an entity to a raw data array of input data.
+ * @return array<string, mixed>
+ */
+ public function entityToInputData(Entity $entity): array
+ {
+ $inputData = $entity->getAttributes();
+ $inputData['tags'] = $this->entityTagsToInputArray($entity);
+
+ // Add a cover to the data if existing on the original entity
+ if ($entity->cover instanceof Image) {
+ $tmpImgFile = tmpfile();
+ $uploadedFile = $this->imageToUploadedFile($entity->cover, $tmpImgFile);
+ $inputData['image'] = $uploadedFile;
}
- return $copyBook;
+ return $inputData;
}
/**
--- /dev/null
+<?php
+
+namespace BookStack\Entities\Tools;
+
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
+use BookStack\Entities\Models\Page;
+use BookStack\Entities\Repos\BookRepo;
+use BookStack\Entities\Repos\BookshelfRepo;
+
+class HierarchyTransformer
+{
+ protected BookRepo $bookRepo;
+ protected BookshelfRepo $shelfRepo;
+ protected Cloner $cloner;
+ protected TrashCan $trashCan;
+
+ // TODO - Test setting book cover image from API
+ // Ensure we can update without resetting image accidentally
+ // Ensure api docs correct.
+ // TODO - As above but for shelves.
+
+ public function transformChapterToBook(Chapter $chapter): Book
+ {
+ // TODO - Check permissions before call
+ // Permissions: edit-chapter, delete-chapter, create-book
+ $inputData = $this->cloner->entityToInputData($chapter);
+ $book = $this->bookRepo->create($inputData);
+
+ // TODO - Copy permissions
+
+ /** @var Page $page */
+ foreach ($chapter->pages as $page) {
+ $page->chapter_id = 0;
+ $page->changeBook($book->id);
+ }
+
+ $this->trashCan->destroyEntity($chapter);
+
+ // TODO - Log activity for change
+ return $book;
+ }
+
+ 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, []);
+
+ // TODO - Copy permissions?
+
+ $shelfBookSyncData = [];
+
+ /** @var Chapter $chapter */
+ foreach ($book->chapters as $index => $chapter) {
+ $newBook = $this->transformChapterToBook($chapter);
+ $shelfBookSyncData[$newBook->id] = ['order' => $index];
+ }
+
+ $shelf->books()->sync($shelfBookSyncData);
+
+ if ($book->directPages->count() > 0) {
+ $book->name .= ' ' . trans('entities.pages');
+ } else {
+ $this->trashCan->destroyEntity($book);
+ }
+
+ // TODO - Log activity for change
+ return $shelf;
+ }
+}
\ No newline at end of file
*
* @throws Exception
*/
- protected function destroyEntity(Entity $entity): int
+ public function destroyEntity(Entity $entity): int
{
if ($entity instanceof Page) {
return $this->destroyPage($entity);
{
protected $bookRepo;
- protected $rules = [
- 'create' => [
- 'name' => ['required', 'string', 'max:255'],
- 'description' => ['string', 'max:1000'],
- 'tags' => ['array'],
- ],
- 'update' => [
- 'name' => ['string', 'min:1', 'max:255'],
- 'description' => ['string', 'max:1000'],
- 'tags' => ['array'],
- ],
- ];
-
public function __construct(BookRepo $bookRepo)
{
$this->bookRepo = $bookRepo;
return response('', 204);
}
+
+ protected function rules(): array {
+ return [
+ 'create' => [
+ 'name' => ['required', 'string', 'max:255'],
+ 'description' => ['string', 'max:1000'],
+ 'tags' => ['array'],
+ 'image' => array_merge(['nullable'], $this->getImageValidationRules()),
+ ],
+ 'update' => [
+ 'name' => ['string', 'min:1', 'max:255'],
+ 'description' => ['string', 'max:1000'],
+ 'tags' => ['array'],
+ 'image' => array_merge(['nullable'], $this->getImageValidationRules()),
+ ],
+ ];
+ }
}
{
protected BookshelfRepo $bookshelfRepo;
- protected $rules = [
- 'create' => [
- 'name' => ['required', 'string', 'max:255'],
- 'description' => ['string', 'max:1000'],
- 'books' => ['array'],
- 'tags' => ['array'],
- ],
- 'update' => [
- 'name' => ['string', 'min:1', 'max:255'],
- 'description' => ['string', 'max:1000'],
- 'books' => ['array'],
- 'tags' => ['array'],
- ],
- ];
-
/**
* BookshelfApiController constructor.
*/
return response('', 204);
}
+
+ protected function rules(): array
+ {
+ return [
+ 'create' => [
+ 'name' => ['required', 'string', 'max:255'],
+ 'description' => ['string', 'max:1000'],
+ 'books' => ['array'],
+ 'tags' => ['array'],
+ 'image' => array_merge(['nullable'], $this->getImageValidationRules()),
+ ],
+ 'update' => [
+ 'name' => ['string', 'min:1', 'max:255'],
+ 'description' => ['string', 'max:1000'],
+ 'books' => ['array'],
+ 'tags' => ['array'],
+ 'image' => array_merge(['nullable'], $this->getImageValidationRules()),
+ ],
+ ];
+ }
}
}
$book = $this->bookRepo->create($request->all());
- $this->bookRepo->updateCoverImage($book, $request->file('image', null));
if ($bookshelf) {
$bookshelf->appendBook($book);
{
$book = $this->bookRepo->getBySlug($slug);
$this->checkOwnablePermission('book-update', $book);
- $this->validate($request, [
+
+ $validated = $this->validate($request, [
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
]);
- $book = $this->bookRepo->update($book, $request->all());
- $resetCover = $request->has('image_reset');
- $this->bookRepo->updateCoverImage($book, $request->file('image', null), $resetCover);
+ if ($request->has('image_reset')) {
+ $validated['image'] = null;
+ } else if (is_null($validated['image'])) {
+ unset($validated['image']);
+ }
+
+ $book = $this->bookRepo->update($book, $validated);
return redirect($book->getUrl());
}
public function store(Request $request)
{
$this->checkPermission('bookshelf-create-all');
- $this->validate($request, [
+ $validated = $this->validate($request, [
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
]);
$bookIds = explode(',', $request->get('books', ''));
- $shelf = $this->bookshelfRepo->create($request->all(), $bookIds);
- $this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null));
+ $shelf = $this->bookshelfRepo->create($validated, $bookIds);
return redirect($shelf->getUrl());
}
{
$shelf = $this->bookshelfRepo->getBySlug($slug);
$this->checkOwnablePermission('bookshelf-update', $shelf);
- $this->validate($request, [
+ $validated = $this->validate($request, [
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
]);
+ if ($request->has('image_reset')) {
+ $validated['image'] = null;
+ } else if (is_null($validated['image'])) {
+ unset($validated['image']);
+ }
+
$bookIds = explode(',', $request->get('books', ''));
- $shelf = $this->bookshelfRepo->update($shelf, $request->all(), $bookIds);
- $resetCover = $request->has('image_reset');
- $this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null), $resetCover);
+ $shelf = $this->bookshelfRepo->update($shelf, $validated, $bookIds);
return redirect($shelf->getUrl());
}
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
+use BookStack\Entities\Repos\BaseRepo;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Repos\BookshelfRepo;
use Illuminate\Support\Str;
$this->assertArrayNotHasKey('image', $tags);
// Test image set if image has cover image
- $shelfRepo = app(BookshelfRepo::class);
- $shelfRepo->updateCoverImage($shelf, $this->getTestImage('image.png'));
+ $baseRepo = app(BaseRepo::class);
+ $baseRepo->updateCoverImage($shelf, $this->getTestImage('image.png'));
$resp = $this->asEditor()->get($shelf->getUrl());
$tags = $this->getOpenGraphTags($resp);