diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 9335e0a70..a1af29de2 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -130,7 +130,7 @@ class ChapterController extends Controller $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); - $this->chapterRepo->update($chapter, $validated); + $chapter = $this->chapterRepo->update($chapter, $validated); return redirect($chapter->getUrl()); } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 34b8a81bb..b49cbb673 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -59,6 +59,9 @@ class Book extends Entity } } + // TODO - Still handle cover as relation through containerData (since it's used in code) + // TODO - Remove above since we can access that via containerData + /** * Get the Page that is used as default template for newly created pages within this Book. */ diff --git a/app/Entities/Models/BookChild.php b/app/Entities/Models/BookChild.php index ad54fb926..90c812f76 100644 --- a/app/Entities/Models/BookChild.php +++ b/app/Entities/Models/BookChild.php @@ -3,7 +3,6 @@ namespace BookStack\Entities\Models; use BookStack\References\ReferenceUpdater; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** @@ -27,25 +26,25 @@ abstract class BookChild extends Entity /** * Change the book that this entity belongs to. */ - public function changeBook(int $newBookId): Entity + public function changeBook(int $newBookId): self { - $oldUrl = $this->getUrl(); - $this->book_id = $newBookId; - $this->refreshSlug(); - $this->save(); - $this->refresh(); + $altered = $this->clone()->refresh(); + $oldUrl = $altered->getUrl(); + $altered->book_id = $newBookId; + $altered->refreshSlug(); + $altered->save(); - if ($oldUrl !== $this->getUrl()) { - app()->make(ReferenceUpdater::class)->updateEntityReferences($this, $oldUrl); + if ($oldUrl !== $altered->getUrl()) { + app()->make(ReferenceUpdater::class)->updateEntityReferences($altered, $oldUrl); } // Update all child pages if a chapter - if ($this instanceof Chapter) { - foreach ($this->pages()->withTrashed()->get() as $page) { + if ($altered instanceof Chapter) { + foreach ($altered->pages()->withTrashed()->get() as $page) { $page->changeBook($newBookId); } } - return $this; + return $altered; } } diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index da4356647..4f4bc43c3 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -59,6 +59,9 @@ class Bookshelf extends Entity } } + // TODO - Still handle cover as relation through containerData (since it's used in code) + // TODO - Remove above since we can access that via containerData + /** * Check if this shelf contains the given book. */ diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 5f372d24b..dee42c1b7 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -101,7 +101,7 @@ abstract class Entity extends Model implements } /** - * Get the container-specific data for this page. + * Get the container-specific data for this item. */ public function containerData(): HasOne { diff --git a/app/Entities/Models/EntityContainerData.php b/app/Entities/Models/EntityContainerData.php index 57ee02b9e..e0d30f56f 100644 --- a/app/Entities/Models/EntityContainerData.php +++ b/app/Entities/Models/EntityContainerData.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Models; use BookStack\Uploads\Image; use BookStack\Util\HtmlContentFilter; +use Exception; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne; @@ -27,6 +28,22 @@ class EntityContainerData extends Model return $this->hasOne(Image::class, 'image_id'); } + /** + * Returns a shelf cover image URL, if cover not exists return default cover image. + */ + public function getCoverUrl(int $width = 440, int $height = 250, string|null $default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='): string|null + { + if (!$this->image_id) { + return $default; + } + + try { + return $this->cover->getThumb($width, $height, false) ?? $default; + } catch (Exception $err) { + return $default; + } + } + /** * Check if this data supports having a default template assigned. */ @@ -46,7 +63,7 @@ class EntityContainerData extends Model /** * Get the description as a cleaned/handled HTML string. */ - public function descriptionHtml(bool $raw = false): string + public function getDescriptionHtml(bool $raw = false): string { $html = $this->description_html ?: '

' . nl2br(e($this->description)) . '

'; if ($raw) { @@ -69,7 +86,7 @@ class EntityContainerData extends Model } if (empty($html) && !empty($plaintext)) { - $this->description_html = $this->descriptionHtml(); + $this->description_html = $this->getDescriptionHtml(); } } } diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index eea1ec1fa..e74324732 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -3,9 +3,7 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\TagRepo; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\BookChild; -use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\EntityContainerData; use BookStack\Entities\Queries\PageQueries; @@ -31,9 +29,13 @@ class BaseRepo /** * Create a new entity in the system. + * @template T of Entity + * @param T $entity + * @return T */ - public function create(Entity $entity, array $input): void + public function create(Entity $entity, array $input): Entity { + $entity = $entity->clone()->refresh(); $entityInput = array_intersect_key($input, ['name', 'priority']); $entity->forceFill($entityInput); $entity->forceFill([ @@ -59,13 +61,19 @@ class BaseRepo $entity->indexForSearch(); $this->referenceStore->updateForEntity($entity); + + return $entity; } /** * Update the given entity. + * @template T of Entity + * @param T $entity + * @return T */ - public function update(Entity $entity, array $input): void + public function update(Entity $entity, array $input): Entity { + $entity = $entity->clone()->refresh(); $oldUrl = $entity->getUrl(); $entity->fill($input); @@ -78,6 +86,7 @@ class BaseRepo $entity->save(); if ($entity->shouldHaveContainerData() && $entity->containerData) { $this->updateContainerDescription($entity->containerData, $input); + $entity->containerData->save(); } if (isset($input['tags'])) { @@ -91,6 +100,8 @@ class BaseRepo if ($oldUrl !== $entity->getUrl()) { $this->referenceUpdater->updateEntityReferences($entity, $oldUrl); } + + return $entity; } /** @@ -147,7 +158,7 @@ class BaseRepo } /** - * Sort the parent of the given entity, if any auto sort actions are set for it. + * Sort the parent of the given entity if any auto sort actions are set for it. * Typically ran during create/update/insert events. */ public function sortParent(Entity $entity): void @@ -158,6 +169,9 @@ class BaseRepo } } + /** + * Update the description of the given container data from input data. + */ protected function updateContainerDescription(EntityContainerData $data, array $input): void { if (isset($input['description_html'])) { diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 207b45396..c689a8413 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -30,9 +30,7 @@ class BookRepo public function create(array $input): Book { return (new DatabaseTransaction(function () use ($input) { - $book = new Book(); - - $this->baseRepo->create($book, $input); + $book = $this->baseRepo->create(new Book(), $input); $this->baseRepo->updateCoverImage($book->containerData, $input['image'] ?? null); $this->baseRepo->updateDefaultTemplate($book->containerData, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::BOOK_CREATE, $book); @@ -52,7 +50,7 @@ class BookRepo */ public function update(Book $book, array $input): Book { - $this->baseRepo->update($book, $input); + $book = $this->baseRepo->update($book, $input); if (array_key_exists('default_template_id', $input)) { $this->baseRepo->updateDefaultTemplate($book->containerData, intval($input['default_template_id'])); diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php index 56fb6932f..3185c4f04 100644 --- a/app/Entities/Repos/BookshelfRepo.php +++ b/app/Entities/Repos/BookshelfRepo.php @@ -25,8 +25,7 @@ class BookshelfRepo public function create(array $input, array $bookIds): Bookshelf { return (new DatabaseTransaction(function () use ($input, $bookIds) { - $shelf = new Bookshelf(); - $this->baseRepo->create($shelf, $input); + $shelf = $this->baseRepo->create(new Bookshelf(), $input); $this->baseRepo->updateCoverImage($shelf->containerData, $input['image'] ?? null); $this->updateBooks($shelf, $bookIds); Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf); @@ -39,7 +38,7 @@ class BookshelfRepo */ public function update(Bookshelf $shelf, array $input, ?array $bookIds): Bookshelf { - $this->baseRepo->update($shelf, $input); + $shelf = $this->baseRepo->update($shelf, $input); if (!is_null($bookIds)) { $this->updateBooks($shelf, $bookIds); diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index ba91e7d06..59b7036d8 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -33,7 +33,8 @@ class ChapterRepo $chapter = new Chapter(); $chapter->book_id = $parentBook->id; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; - $this->baseRepo->create($chapter, $input); + + $chapter = $this->baseRepo->create($chapter, $input); $this->baseRepo->updateDefaultTemplate($chapter->containerData, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::CHAPTER_CREATE, $chapter); @@ -48,7 +49,7 @@ class ChapterRepo */ public function update(Chapter $chapter, array $input): Chapter { - $this->baseRepo->update($chapter, $input); + $chapter = $this->baseRepo->update($chapter, $input); if (array_key_exists('default_template_id', $input)) { $this->baseRepo->updateDefaultTemplate($chapter->containerData, intval($input['default_template_id'])); @@ -93,7 +94,7 @@ class ChapterRepo } return (new DatabaseTransaction(function () use ($chapter, $parent) { - $chapter->changeBook($parent->id); + $chapter = $chapter->changeBook($parent->id); $chapter->rebuildPermissions(); Activity::add(ActivityType::CHAPTER_MOVE, $chapter); diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 48c815ced..fdb9d66f3 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -85,7 +85,9 @@ class PageRepo $draft->pageData->revision_count = 1; $draft->pageData->priority = $this->getNewPriority($draft); $this->updateTemplateStatusAndContentFromInput($draft, $input); - $this->baseRepo->update($draft, $input); + + $draft = $this->baseRepo->update($draft, $input); + $draft->pageData->save(); $draft->rebuildPermissions(); $summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision'); @@ -115,13 +117,15 @@ class PageRepo */ public function update(Page $page, array $input): Page { + $page = $page->clone()->refresh(); + // Hold the old details to compare later $oldName = $page->name; $oldHtml = $page->pageData->html; $oldMarkdown = $page->pageData->markdown; $this->updateTemplateStatusAndContentFromInput($page, $input); - $this->baseRepo->update($page, $input); + $page = $this->baseRepo->update($page, $input); // Update with new details $page->pageData->revision_count++; @@ -187,6 +191,7 @@ class PageRepo if ($page->draft) { $this->updateTemplateStatusAndContentFromInput($page, $input); $page->forceFill(array_intersect_key($input, array_flip(['name'])))->save(); + $page->pageData->save(); $page->save(); return $page; @@ -285,7 +290,7 @@ class PageRepo return (new DatabaseTransaction(function () use ($page, $parent) { $page->pageData->chapter_id = ($parent instanceof Chapter) ? $parent->id : null; $newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id; - $page->changeBook($newBookId); + $page = $page->changeBook($newBookId); $page->rebuildPermissions(); Activity::add(ActivityType::PAGE_MOVE, $page); diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 7dd3f3e11..4bbab6265 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -3,13 +3,10 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; -use BookStack\Sorting\BookSortMap; -use BookStack\Sorting\BookSortMapItem; use Illuminate\Support\Collection; class BookContents @@ -29,7 +26,7 @@ class BookContents { $maxPage = $this->book->pages() ->where('draft', '=', false) - ->where('chapter_id', '=', 0) + ->whereDoesntHave('chapter') ->max('priority'); $maxChapter = $this->book->chapters() @@ -80,11 +77,11 @@ class BookContents protected function bookChildSortFunc(): callable { return function (Entity $entity) { - if (isset($entity['draft']) && $entity['draft']) { + if ($entity->getAttribute('draft') ?? false) { return -100; } - return $entity['priority'] ?? 0; + return $entity->getAttribute('priority') ?? 0; }; } diff --git a/app/Entities/Tools/HierarchyTransformer.php b/app/Entities/Tools/HierarchyTransformer.php index b0d8880f4..fa45fcd11 100644 --- a/app/Entities/Tools/HierarchyTransformer.php +++ b/app/Entities/Tools/HierarchyTransformer.php @@ -34,6 +34,7 @@ class HierarchyTransformer /** @var Page $page */ foreach ($chapter->pages as $page) { $page->chapter_id = 0; + $page->save(); $page->changeBook($book->id); } diff --git a/app/Exports/ExportFormatter.php b/app/Exports/ExportFormatter.php index 9515207e4..cccf0d51e 100644 --- a/app/Exports/ExportFormatter.php +++ b/app/Exports/ExportFormatter.php @@ -318,7 +318,7 @@ class ExportFormatter { $text = '# ' . $chapter->name . "\n\n"; - $description = (new HtmlToMarkdown($chapter->containerData->descriptionHtml()))->convert(); + $description = (new HtmlToMarkdown($chapter->containerData->getDescriptionHtml()))->convert(); if ($description) { $text .= $description . "\n\n"; } @@ -338,7 +338,7 @@ class ExportFormatter $bookTree = (new BookContents($book))->getTree(false, true); $text = '# ' . $book->name . "\n\n"; - $description = (new HtmlToMarkdown($book->containerData->descriptionHtml()))->convert(); + $description = (new HtmlToMarkdown($book->containerData->getDescriptionHtml()))->convert(); if ($description) { $text .= $description . "\n\n"; } diff --git a/app/Exports/ZipExports/Models/ZipExportBook.php b/app/Exports/ZipExports/Models/ZipExportBook.php index 5dd3775c3..7e7f472d7 100644 --- a/app/Exports/ZipExports/Models/ZipExportBook.php +++ b/app/Exports/ZipExports/Models/ZipExportBook.php @@ -55,7 +55,7 @@ final class ZipExportBook extends ZipExportModel $instance = new self(); $instance->id = $model->id; $instance->name = $model->name; - $instance->description_html = $model->containerData->descriptionHtml(); + $instance->description_html = $model->containerData->getDescriptionHtml(); if ($model->containerData->cover) { $instance->cover = $files->referenceForImage($model->containerData->cover); diff --git a/app/Exports/ZipExports/Models/ZipExportChapter.php b/app/Exports/ZipExports/Models/ZipExportChapter.php index f27f117a7..06d615b1a 100644 --- a/app/Exports/ZipExports/Models/ZipExportChapter.php +++ b/app/Exports/ZipExports/Models/ZipExportChapter.php @@ -40,7 +40,7 @@ final class ZipExportChapter extends ZipExportModel $instance = new self(); $instance->id = $model->id; $instance->name = $model->name; - $instance->description_html = $model->containerData->descriptionHtml(); + $instance->description_html = $model->containerData->getDescriptionHtml(); $instance->priority = $model->priority; $instance->tags = ZipExportTag::fromModelArray($model->tags()->get()->all()); diff --git a/app/References/ReferenceUpdater.php b/app/References/ReferenceUpdater.php index c4f903da7..f80f1af07 100644 --- a/app/References/ReferenceUpdater.php +++ b/app/References/ReferenceUpdater.php @@ -70,7 +70,7 @@ class ReferenceUpdater protected function updateReferencesWithinDescription(EntityContainerData $containerData, string $oldLink, string $newLink): void { - $html = $this->updateLinksInHtml($containerData->descriptionHtml(true) ?: '', $oldLink, $newLink); + $html = $this->updateLinksInHtml($containerData->getDescriptionHtml(true) ?: '', $oldLink, $newLink); $containerData->setDescriptionHtml($html); $containerData->save(); } diff --git a/app/Sorting/BookSorter.php b/app/Sorting/BookSorter.php index 1152101d2..77180a296 100644 --- a/app/Sorting/BookSorter.php +++ b/app/Sorting/BookSorter.php @@ -155,7 +155,7 @@ class BookSorter // Action the required changes if ($bookChanged) { - $model->changeBook($newBook->id); + $model = $model->changeBook($newBook->id); } if ($model instanceof Page && $chapterChanged) { diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index c79aa599e..bd7f858cf 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -18,7 +18,7 @@ @include('form.image-picker', [ 'defaultImage' => url('/book_default_cover.png'), - 'currentImage' => (isset($model) && $model->containerData->cover) ? $model->getBookCover() : url('/book_default_cover.png') , + 'currentImage' => (($model ?? null)?->containerData?->getCoverUrl(440, 250, url('/book_default_cover.png')) ?? url('/book_default_cover.png')), 'name' => 'image', 'imageClass' => 'cover' ]) diff --git a/resources/views/books/parts/list-item.blade.php b/resources/views/books/parts/list-item.blade.php index 88c314d82..a3ff0971f 100644 --- a/resources/views/books/parts/list-item.blade.php +++ b/resources/views/books/parts/list-item.blade.php @@ -5,7 +5,7 @@

{{ $book->name }}

-

{{ $book->containerData->description }}

+

{{ $book->description }}

\ No newline at end of file diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 8dceaf2c1..d89c38659 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -7,7 +7,7 @@ @stop @push('social-meta') - + @if($book->containerData->cover) @endif @@ -26,7 +26,7 @@

{{$book->name}}

-
{!! $book->containerData->descriptionHtml() !!}
+
{!! $book->containerData->getDescriptionHtml() !!}
@if(count($bookChildren) > 0)
@foreach($bookChildren as $childElement) diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 8566c96e6..d3b90f640 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -7,7 +7,7 @@ @stop @push('social-meta') - + @endpush @include('entities.body-tag-classes', ['entity' => $chapter]) @@ -24,7 +24,7 @@

{{ $chapter->name }}

-
{!! $chapter->containerData->descriptionHtml() !!}
+
{!! $chapter->containerData->getDescriptionHtml() !!}
@if(count($pages) > 0)
@foreach($pages as $page) diff --git a/resources/views/entities/grid-item.blade.php b/resources/views/entities/grid-item.blade.php index 6028d85df..2f0aacc46 100644 --- a/resources/views/entities/grid-item.blade.php +++ b/resources/views/entities/grid-item.blade.php @@ -1,7 +1,7 @@
-
{!! $shelf->containerData->descriptionHtml() !!}
+
{!! $shelf->containerData->getDescriptionHtml() !!}
@if(count($sortedVisibleShelfBooks) > 0) @if($view === 'list')
diff --git a/tests/Exports/ZipExportTest.php b/tests/Exports/ZipExportTest.php index 5e7ba4b56..55e7d12a4 100644 --- a/tests/Exports/ZipExportTest.php +++ b/tests/Exports/ZipExportTest.php @@ -227,7 +227,7 @@ class ZipExportTest extends TestCase $bookData = $zip->data['book']; $this->assertEquals($book->id, $bookData['id']); $this->assertEquals($book->name, $bookData['name']); - $this->assertEquals($book->containerData->descriptionHtml(), $bookData['description_html']); + $this->assertEquals($book->containerData->getDescriptionHtml(), $bookData['description_html']); $this->assertCount(2, $bookData['tags']); $this->assertCount($book->directPages()->count(), $bookData['pages']); $this->assertCount($book->chapters()->count(), $bookData['chapters']); @@ -264,7 +264,7 @@ class ZipExportTest extends TestCase $chapterData = $zip->data['chapter']; $this->assertEquals($chapter->id, $chapterData['id']); $this->assertEquals($chapter->name, $chapterData['name']); - $this->assertEquals($chapter->containerData->descriptionHtml(), $chapterData['description_html']); + $this->assertEquals($chapter->containerData->getDescriptionHtml(), $chapterData['description_html']); $this->assertCount(2, $chapterData['tags']); $this->assertEquals($chapter->priority, $chapterData['priority']); $this->assertCount($chapter->pages()->count(), $chapterData['pages']);