Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/Actions/ActivityType.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ class ActivityType
const CHAPTER_MOVE = 'chapter_move';

const BOOK_CREATE = 'book_create';
const BOOK_CREATE_FROM_CHAPTER = 'book_create_from_chapter';
const BOOK_UPDATE = 'book_update';
const BOOK_DELETE = 'book_delete';
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';

Expand Down
6 changes: 6 additions & 0 deletions app/Entities/Repos/BookRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public function create(array $input): Book
{
$book = new Book();
$this->baseRepo->create($book, $input);
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
Activity::add(ActivityType::BOOK_CREATE, $book);

return $book;
Expand All @@ -102,6 +103,11 @@ public function create(array $input): Book
public function update(Book $book, array $input): Book
{
$this->baseRepo->update($book, $input);

if (array_key_exists('image', $input)) {
$this->baseRepo->updateCoverImage($book, $input['image'], $input['image'] === null);
}

Activity::add(ActivityType::BOOK_UPDATE, $book);

return $book;
Expand Down
21 changes: 6 additions & 15 deletions app/Entities/Repos/BookshelfRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity;
use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;

class BookshelfRepo
Expand Down Expand Up @@ -89,6 +87,7 @@ public function create(array $input, array $bookIds): Bookshelf
{
$shelf = new Bookshelf();
$this->baseRepo->create($shelf, $input);
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
$this->updateBooks($shelf, $bookIds);
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);

Expand All @@ -106,14 +105,17 @@ public function update(Bookshelf $shelf, array $input, ?array $bookIds): Bookshe
$this->updateBooks($shelf, $bookIds);
}

if (array_key_exists('image', $input)) {
$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)
Expand All @@ -132,17 +134,6 @@ 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.
*/
Expand Down
17 changes: 0 additions & 17 deletions app/Entities/Repos/PageRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,23 +392,6 @@ public function findParentByIdentifier(string $identifier): ?Entity
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.
Expand Down
76 changes: 39 additions & 37 deletions app/Entities/Tools/Cloner.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,10 @@

class Cloner
{
/**
* @var PageRepo
*/
protected $pageRepo;

/**
* @var ChapterRepo
*/
protected $chapterRepo;

/**
* @var BookRepo
*/
protected $bookRepo;

/**
* @var ImageService
*/
protected $imageService;
protected PageRepo $pageRepo;
protected ChapterRepo $chapterRepo;
protected BookRepo $bookRepo;
protected ImageService $imageService;

public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo, BookRepo $bookRepo, ImageService $imageService)
{
Expand All @@ -50,11 +35,8 @@ public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo, BookRe
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);
}
Expand All @@ -65,9 +47,8 @@ public function clonePage(Page $original, Entity $parent, string $newName): Page
*/
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);

Expand All @@ -87,9 +68,8 @@ public function cloneChapter(Chapter $original, Book $parent, string $newName):
*/
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);

Expand All @@ -104,26 +84,48 @@ public function cloneBook(Book $original, string $newName): Book
}
}

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) {
$uploadedFile = $this->imageToUploadedFile($entity->cover);
$inputData['image'] = $uploadedFile;
}

return $copyBook;
return $inputData;
}

/**
* Copy the permission settings from the source entity to the target entity.
*/
public function copyEntityPermissions(Entity $sourceEntity, Entity $targetEntity): void
{
$targetEntity->restricted = $sourceEntity->restricted;
$permissions = $sourceEntity->permissions()->get(['role_id', 'action'])->toArray();
$targetEntity->permissions()->delete();
$targetEntity->permissions()->createMany($permissions);
$targetEntity->rebuildPermissions();
}

/**
* Convert an image instance to an UploadedFile instance to mimic
* a file being uploaded.
*/
protected function imageToUploadedFile(Image $image, &$tmpFile): ?UploadedFile
protected function imageToUploadedFile(Image $image): ?UploadedFile
{
$imgData = $this->imageService->getImageData($image);
$tmpImgFilePath = stream_get_meta_data($tmpFile)['uri'];
$tmpImgFilePath = tempnam(sys_get_temp_dir(), 'bs_cover_clone_');
file_put_contents($tmpImgFilePath, $imgData);

return new UploadedFile($tmpImgFilePath, basename($image->path));
Expand Down
87 changes: 87 additions & 0 deletions app/Entities/Tools/HierarchyTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace BookStack\Entities\Tools;

use BookStack\Actions\ActivityType;
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;
use BookStack\Facades\Activity;

class HierarchyTransformer
{
protected BookRepo $bookRepo;
protected BookshelfRepo $shelfRepo;
protected Cloner $cloner;
protected TrashCan $trashCan;

public function __construct(BookRepo $bookRepo, BookshelfRepo $shelfRepo, Cloner $cloner, TrashCan $trashCan)
{
$this->bookRepo = $bookRepo;
$this->shelfRepo = $shelfRepo;
$this->cloner = $cloner;
$this->trashCan = $trashCan;
}

/**
* Transform a chapter into a book.
* Does not check permissions, check before calling.
*/
public function transformChapterToBook(Chapter $chapter): Book
{
$inputData = $this->cloner->entityToInputData($chapter);
$book = $this->bookRepo->create($inputData);
$this->cloner->copyEntityPermissions($chapter, $book);

/** @var Page $page */
foreach ($chapter->pages as $page) {
$page->chapter_id = 0;
$page->changeBook($book->id);
}

$this->trashCan->destroyEntity($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
{
$inputData = $this->cloner->entityToInputData($book);
$shelf = $this->shelfRepo->create($inputData, []);
$this->cloner->copyEntityPermissions($book, $shelf);

$shelfBookSyncData = [];

/** @var Chapter $chapter */
foreach ($book->chapters as $index => $chapter) {
$newBook = $this->transformChapterToBook($chapter);
$shelfBookSyncData[$newBook->id] = ['order' => $index];
if (!$newBook->restricted) {
$this->cloner->copyEntityPermissions($shelf, $newBook);
}
}

if ($book->directPages->count() > 0) {
$book->name .= ' ' . trans('entities.pages');
$shelfBookSyncData[$book->id] = ['order' => count($shelfBookSyncData) + 1];
$book->save();
} else {
$this->trashCan->destroyEntity($book);
}

$shelf->books()->sync($shelfBookSyncData);

Activity::add(ActivityType::BOOKSHELF_CREATE_FROM_BOOK, $shelf);

return $shelf;
}
}
2 changes: 1 addition & 1 deletion app/Entities/Tools/TrashCan.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ protected function restoreEntity(Entity $entity): int
*
* @throws Exception
*/
protected function destroyEntity(Entity $entity): int
public function destroyEntity(Entity $entity): int
{
if ($entity instanceof Page) {
return $this->destroyPage($entity);
Expand Down
Loading