From 70bfebcd7c9396c9d17757ec57a584371dce2e9f Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 1 Jan 2024 21:58:49 +0100 Subject: [PATCH 1/8] Added Default Templates for Chapters --- .../Controllers/ChapterController.php | 14 ++++---- app/Entities/Controllers/PageController.php | 9 +++-- app/Entities/Models/Chapter.php | 11 ++++++ app/Entities/Repos/ChapterRepo.php | 34 +++++++++++++++++++ app/Entities/Repos/PageRepo.php | 8 ++++- app/Entities/Tools/TrashCan.php | 6 ++++ ...04542_add_default_template_to_chapters.php | 32 +++++++++++++++++ lang/en/entities.php | 3 ++ resources/views/chapters/parts/form.blade.php | 23 +++++++++++++ 9 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2024_01_01_104542_add_default_template_to_chapters.php diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 28ad35fa4..00616888a 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -49,9 +49,10 @@ class ChapterController extends Controller public function store(Request $request, string $bookSlug) { $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'default_template_id' => ['nullable', 'integer'], ]); $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); @@ -111,9 +112,10 @@ class ChapterController extends Controller public function update(Request $request, string $bookSlug, string $chapterSlug) { $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'default_template_id' => ['nullable', 'integer'], ]); $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index adafcdc7b..74dd4f531 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -6,6 +6,7 @@ use BookStack\Activity\Models\View; use BookStack\Activity\Tools\CommentTree; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\BookContents; @@ -259,7 +260,9 @@ class PageController extends Controller $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0; + $usedAsTemplate = + Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || + Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, @@ -279,7 +282,9 @@ class PageController extends Controller $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0; + $usedAsTemplate = + Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || + Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index f30d77b5c..d3a710111 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -2,6 +2,7 @@ namespace BookStack\Entities\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; @@ -11,6 +12,8 @@ use Illuminate\Support\Collection; * * @property Collection $pages * @property string $description + * @property ?int $default_template_id + * @property ?Page $defaultTemplate */ class Chapter extends BookChild { @@ -48,6 +51,14 @@ class Chapter extends BookChild return url('/' . implode('/', $parts)); } + /** + * Get the Page that is used as default template for newly created pages within this Chapter. + */ + public function defaultTemplate(): BelongsTo + { + return $this->belongsTo(Page::class, 'default_template_id'); + } + /** * Get the visible pages in this chapter. */ diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index 977193d85..9534a4060 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\ActivityType; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Tools\BookContents; @@ -46,6 +47,7 @@ class ChapterRepo $chapter->book_id = $parentBook->id; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; $this->baseRepo->create($chapter, $input); + $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::CHAPTER_CREATE, $chapter); return $chapter; @@ -57,6 +59,11 @@ class ChapterRepo public function update(Chapter $chapter, array $input): Chapter { $this->baseRepo->update($chapter, $input); + + if (array_key_exists('default_template_id', $input)) { + $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'])); + } + Activity::add(ActivityType::CHAPTER_UPDATE, $chapter); return $chapter; @@ -101,6 +108,33 @@ class ChapterRepo return $parent; } + /** + * Update the default page template used for this chapter. + * Checks that, if changing, the provided value is a valid template and the user + * has visibility of the provided page template id. + */ + protected function updateChapterDefaultTemplate(Chapter $chapter, int $templateId): void + { + $changing = $templateId !== intval($chapter->default_template_id); + if (!$changing) { + return; + } + + if ($templateId === 0) { + $chapter->default_template_id = null; + $chapter->save(); + return; + } + + $templateExists = Page::query()->visible() + ->where('template', '=', true) + ->where('id', '=', $templateId) + ->exists(); + + $chapter->default_template_id = $templateExists ? $templateId : null; + $chapter->save(); + } + /** * Find a page parent entity via an identifier string in the format: * {type}:{id} diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 7b14ea7d2..67c4b2225 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -136,7 +136,13 @@ class PageRepo $page->book_id = $parent->id; } - $defaultTemplate = $page->book->defaultTemplate; + // check for chapter + if ($page->chapter_id) { + $defaultTemplate = $page->chapter->defaultTemplate; + } else { + $defaultTemplate = $page->book->defaultTemplate; + } + if ($defaultTemplate && userCan('view', $defaultTemplate)) { $page->forceFill([ 'html' => $defaultTemplate->html, diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index b25103985..e5bcfe71a 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -208,6 +208,12 @@ class TrashCan $page->forceDelete(); + // Remove chapter template usages + Chapter::query()->where('default_template_id', '=', $page->id) + ->update(['default_template_id' => null]); + + $page->forceDelete(); + return 1; } diff --git a/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php new file mode 100644 index 000000000..5e9ea1de3 --- /dev/null +++ b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php @@ -0,0 +1,32 @@ +integer('default_template_id')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('chapters', function (Blueprint $table) { + $table->dropColumn('default_template_id'); + }); + } +} diff --git a/lang/en/entities.php b/lang/en/entities.php index f1f915544..4ab9de47d 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -192,6 +192,9 @@ return [ 'chapters_permissions_success' => 'Chapter Permissions Updated', 'chapters_search_this' => 'Search this chapter', 'chapter_sort_book' => 'Sort Book', + 'chapter_default_template' => 'Default Page Template', + 'chapter_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'chapter_default_template_select' => 'Select a template page', // Pages 'page' => 'Page', diff --git a/resources/views/chapters/parts/form.blade.php b/resources/views/chapters/parts/form.blade.php index c6052c93a..ea7f84bc8 100644 --- a/resources/views/chapters/parts/form.blade.php +++ b/resources/views/chapters/parts/form.blade.php @@ -22,6 +22,29 @@ +
+ +
+
+

+ {{ trans('entities.chapter_default_template_explain') }} +

+ +
+ @include('form.page-picker', [ + 'name' => 'default_template_id', + 'placeholder' => trans('entities.chapter_default_template_select'), + 'value' => $chapter->default_template_id ?? null, + 'selectorEndpoint' => '/search/entity-selector-templates', + ]) +
+
+ +
+
+
{{ trans('common.cancel') }} From 8c6b1164724fffd1766792fb3d6fef4972ba1b9e Mon Sep 17 00:00:00 2001 From: Sascha Date: Tue, 23 Jan 2024 21:37:00 +0100 Subject: [PATCH 2/8] Update TrashCan.php remove duplicate call of $page->forceDelete(); --- app/Entities/Tools/TrashCan.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index e5bcfe71a..8e9f010df 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -206,8 +206,6 @@ class TrashCan Book::query()->where('default_template_id', '=', $page->id) ->update(['default_template_id' => null]); - $page->forceDelete(); - // Remove chapter template usages Chapter::query()->where('default_template_id', '=', $page->id) ->update(['default_template_id' => null]); From 0fc02a2532fd33c443838de77f5700df220b79c9 Mon Sep 17 00:00:00 2001 From: Sascha Date: Tue, 23 Jan 2024 22:37:15 +0100 Subject: [PATCH 3/8] fixed error from phpcs --- app/Entities/Controllers/PageController.php | 4 ++-- app/Entities/Repos/PageRepo.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 74dd4f531..eaad3c0b7 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -260,7 +260,7 @@ class PageController extends Controller $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = + $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; @@ -282,7 +282,7 @@ class PageController extends Controller $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = + $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 67c4b2225..d9bda0198 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -142,7 +142,7 @@ class PageRepo } else { $defaultTemplate = $page->book->defaultTemplate; } - + if ($defaultTemplate && userCan('view', $defaultTemplate)) { $page->forceFill([ 'html' => $defaultTemplate->html, From 2a849894bee6ac7846261938c9606fb18a4a1761 Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 29 Jan 2024 19:37:59 +0100 Subject: [PATCH 4/8] Update entities.php changed text of `pages_delete_warning_template` to include chapters --- lang/en/entities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/entities.php b/lang/en/entities.php index 4ab9de47d..21ef93fed 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -210,7 +210,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Page deleted', 'pages_delete_draft_success' => 'Draft page deleted', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'This page is in active use as a book or chapter default page template. These books or chapters will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Are you sure you want to delete this page?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', From 64c783c6f8efb351dd73a1cfb4b5f214a731bd57 Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 29 Jan 2024 19:55:39 +0100 Subject: [PATCH 5/8] extraded template form to own file and changed translations --- lang/en/entities.php | 9 +++------ resources/views/books/parts/form.blade.php | 18 ++---------------- resources/views/chapters/parts/form.blade.php | 18 ++---------------- .../views/entities/template-selector.blade.php | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 resources/views/entities/template-selector.blade.php diff --git a/lang/en/entities.php b/lang/en/entities.php index 21ef93fed..8860e243e 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -39,6 +39,9 @@ return [ 'export_pdf' => 'PDF File', 'export_text' => 'Plain Text File', 'export_md' => 'Markdown File', + 'default_template' => 'Default Page Template', + 'default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book/chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'default_template_select' => 'Select a template page', // Permissions and restrictions 'permissions' => 'Permissions', @@ -132,9 +135,6 @@ return [ 'books_edit_named' => 'Edit Book :bookName', 'books_form_book_name' => 'Book Name', 'books_save' => 'Save Book', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Book Permissions', 'books_permissions_updated' => 'Book Permissions Updated', 'books_empty_contents' => 'No pages or chapters have been created for this book.', @@ -192,9 +192,6 @@ return [ 'chapters_permissions_success' => 'Chapter Permissions Updated', 'chapters_search_this' => 'Search this chapter', 'chapter_sort_book' => 'Sort Book', - 'chapter_default_template' => 'Default Page Template', - 'chapter_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'chapter_default_template_select' => 'Select a template page', // Pages 'page' => 'Page', diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index fa8f16e52..ee261e72d 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -40,24 +40,10 @@
-
-

- {{ trans('entities.books_default_template_explain') }} -

- -
- @include('form.page-picker', [ - 'name' => 'default_template_id', - 'placeholder' => trans('entities.books_default_template_select'), - 'value' => $book->default_template_id ?? null, - 'selectorEndpoint' => '/search/entity-selector-templates', - ]) -
-
- + @include('entities.template-selector', ['entity' => $book ?? null])
diff --git a/resources/views/chapters/parts/form.blade.php b/resources/views/chapters/parts/form.blade.php index ea7f84bc8..602693916 100644 --- a/resources/views/chapters/parts/form.blade.php +++ b/resources/views/chapters/parts/form.blade.php @@ -24,24 +24,10 @@
-
-

- {{ trans('entities.chapter_default_template_explain') }} -

- -
- @include('form.page-picker', [ - 'name' => 'default_template_id', - 'placeholder' => trans('entities.chapter_default_template_select'), - 'value' => $chapter->default_template_id ?? null, - 'selectorEndpoint' => '/search/entity-selector-templates', - ]) -
-
- + @include('entities.template-selector', ['entity' => $chapter ?? null])
diff --git a/resources/views/entities/template-selector.blade.php b/resources/views/entities/template-selector.blade.php new file mode 100644 index 000000000..80b2f49b2 --- /dev/null +++ b/resources/views/entities/template-selector.blade.php @@ -0,0 +1,14 @@ +
+

+ {{ trans('entities.default_template_explain') }} +

+ +
+ @include('form.page-picker', [ + 'name' => 'default_template_id', + 'placeholder' => trans('entities.default_template_select'), + 'value' => $entity->default_template_id ?? null, + 'selectorEndpoint' => '/search/entity-selector-templates', + ]) +
+
\ No newline at end of file From 4a8f70240fd01f28980dfd4031c7df2b1db4949a Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 29 Jan 2024 19:59:03 +0100 Subject: [PATCH 6/8] added template to chapter API controller --- .../Controllers/ChapterApiController.php | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index c21323262..27b820659 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -15,20 +15,22 @@ class ChapterApiController extends ApiController { protected $rules = [ 'create' => [ - 'book_id' => ['required', 'integer'], - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1900'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], - 'priority' => ['integer'], + 'book_id' => ['required', 'integer'], + 'name' => ['required', 'string', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], + 'default_template_id' => ['nullable', 'integer'], ], 'update' => [ - 'book_id' => ['integer'], - 'name' => ['string', 'min:1', 'max:255'], - 'description' => ['string', 'max:1900'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], - 'priority' => ['integer'], + 'book_id' => ['integer'], + 'name' => ['string', 'min:1', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], + 'default_template_id' => ['nullable', 'integer'], ], ]; From 4137cf9c8fc1f9088f8792b64fcc889f787e8958 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 1 Feb 2024 12:22:16 +0000 Subject: [PATCH 7/8] Default chapter templates: Updated api docs and tests Also applied minor tweaks to some wording and logic. During review of #4750 --- app/Api/ListingResponseBuilder.php | 2 + app/Entities/Repos/PageRepo.php | 8 +-- ...04542_add_default_template_to_chapters.php | 64 +++++++++---------- dev/api/requests/chapters-create.json | 1 + dev/api/requests/chapters-update.json | 1 + dev/api/responses/chapters-create.json | 1 + dev/api/responses/chapters-read.json | 1 + dev/api/responses/chapters-update.json | 1 + lang/en/entities.php | 2 +- tests/Api/ChaptersApiTest.php | 5 ++ 10 files changed, 46 insertions(+), 40 deletions(-) diff --git a/app/Api/ListingResponseBuilder.php b/app/Api/ListingResponseBuilder.php index 44117bad9..329f5ce1c 100644 --- a/app/Api/ListingResponseBuilder.php +++ b/app/Api/ListingResponseBuilder.php @@ -61,6 +61,8 @@ class ListingResponseBuilder } }); + dd($data->first()); + return response()->json([ 'data' => $data, 'total' => $total, diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index d9bda0198..85237a752 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -136,13 +136,7 @@ class PageRepo $page->book_id = $parent->id; } - // check for chapter - if ($page->chapter_id) { - $defaultTemplate = $page->chapter->defaultTemplate; - } else { - $defaultTemplate = $page->book->defaultTemplate; - } - + $defaultTemplate = $page->chapter->defaultTemplate ?? $page->book->defaultTemplate; if ($defaultTemplate && userCan('view', $defaultTemplate)) { $page->forceFill([ 'html' => $defaultTemplate->html, diff --git a/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php index 5e9ea1de3..b3a103a01 100644 --- a/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php +++ b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php @@ -1,32 +1,32 @@ -integer('default_template_id')->nullable()->default(null); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('chapters', function (Blueprint $table) { - $table->dropColumn('default_template_id'); - }); - } -} +integer('default_template_id')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('chapters', function (Blueprint $table) { + $table->dropColumn('default_template_id'); + }); + } +} diff --git a/dev/api/requests/chapters-create.json b/dev/api/requests/chapters-create.json index e9d903387..02aee9eea 100644 --- a/dev/api/requests/chapters-create.json +++ b/dev/api/requests/chapters-create.json @@ -3,6 +3,7 @@ "name": "My fantastic new chapter", "description_html": "

This is a great new chapter that I've created via the API

", "priority": 15, + "default_template_id": 25, "tags": [ {"name": "Category", "value": "Top Content"}, {"name": "Rating", "value": "Highest"} diff --git a/dev/api/requests/chapters-update.json b/dev/api/requests/chapters-update.json index be675772b..cf9c89eac 100644 --- a/dev/api/requests/chapters-update.json +++ b/dev/api/requests/chapters-update.json @@ -3,6 +3,7 @@ "name": "My fantastic updated chapter", "description_html": "

This is an updated chapter that I've altered via the API

", "priority": 16, + "default_template_id": 2428, "tags": [ {"name": "Category", "value": "Kinda Good Content"}, {"name": "Rating", "value": "Medium"} diff --git a/dev/api/responses/chapters-create.json b/dev/api/responses/chapters-create.json index 183186b0b..1e81a1e63 100644 --- a/dev/api/responses/chapters-create.json +++ b/dev/api/responses/chapters-create.json @@ -11,6 +11,7 @@ "updated_by": 1, "owned_by": 1, "description_html": "

This is a great new chapter<\/strong> that I've created via the API<\/p>", + "default_template_id": 25, "tags": [ { "name": "Category", diff --git a/dev/api/responses/chapters-read.json b/dev/api/responses/chapters-read.json index 192ffce7c..01a2f4b9f 100644 --- a/dev/api/responses/chapters-read.json +++ b/dev/api/responses/chapters-read.json @@ -5,6 +5,7 @@ "name": "Content Creation", "description": "How to create documentation on whatever subject you need to write about.", "description_html": "

How to create documentation on whatever subject you need to write about.

", + "default_template_id": 25, "priority": 3, "created_at": "2019-05-05T21:49:56.000000Z", "updated_at": "2019-09-28T11:24:23.000000Z", diff --git a/dev/api/responses/chapters-update.json b/dev/api/responses/chapters-update.json index 5ac3c64c1..4d8d0024e 100644 --- a/dev/api/responses/chapters-update.json +++ b/dev/api/responses/chapters-update.json @@ -11,6 +11,7 @@ "updated_by": 1, "owned_by": 1, "description_html": "

This is an updated chapter<\/strong> that I've altered via the API<\/p>", + "default_template_id": 2428, "tags": [ { "name": "Category", diff --git a/lang/en/entities.php b/lang/en/entities.php index 8860e243e..9e620b24e 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -40,7 +40,7 @@ return [ 'export_text' => 'Plain Text File', 'export_md' => 'Markdown File', 'default_template' => 'Default Page Template', - 'default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book/chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'default_template_explain' => 'Assign a page template that will be used as the default content for all pages created within this item. Keep in mind this will only be used if the page creator has view access to the chosen template page.', 'default_template_select' => 'Select a template page', // Permissions and restrictions diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 81a918877..fdb80a497 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -35,6 +35,7 @@ class ChaptersApiTest extends TestCase { $this->actingAsApiEditor(); $book = $this->entities->book(); + $templatePage = $this->entities->templatePage(); $details = [ 'name' => 'My API chapter', 'description' => 'A chapter created via the API', @@ -46,6 +47,7 @@ class ChaptersApiTest extends TestCase ], ], 'priority' => 15, + 'default_template_id' => $templatePage->id, ]; $resp = $this->postJson($this->baseEndpoint, $details); @@ -147,6 +149,7 @@ class ChaptersApiTest extends TestCase 'name' => $page->name, ], ], + 'default_template_id' => null, ]); $resp->assertJsonCount($chapter->pages()->count(), 'pages'); } @@ -155,6 +158,7 @@ class ChaptersApiTest extends TestCase { $this->actingAsApiEditor(); $chapter = $this->entities->chapter(); + $templatePage = $this->entities->templatePage(); $details = [ 'name' => 'My updated API chapter', 'description' => 'A chapter updated via the API', @@ -165,6 +169,7 @@ class ChaptersApiTest extends TestCase ], ], 'priority' => 15, + 'default_template_id' => $templatePage->id, ]; $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details); From 43a72fb9a5dd40885c70c41bc578bffbec9c25ee Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 1 Feb 2024 12:51:47 +0000 Subject: [PATCH 8/8] Default chapter templates: Added tests, extracted repo logic - Updated existing book tests to be generic to all default templates, and updated with chapter testing. - Extracted repeated logic in the Book/Chapter repos to be shared in the BaseRepo. Review of #4750 --- app/Entities/Repos/BaseRepo.php | 30 ++ app/Entities/Repos/BookRepo.php | 31 +-- app/Entities/Repos/ChapterRepo.php | 32 +-- tests/Entity/BookDefaultTemplateTest.php | 185 ------------ tests/Entity/DefaultTemplateTest.php | 341 +++++++++++++++++++++++ 5 files changed, 375 insertions(+), 244 deletions(-) delete mode 100644 tests/Entity/BookDefaultTemplateTest.php create mode 100644 tests/Entity/DefaultTemplateTest.php diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 27bf00161..17208ae03 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -3,9 +3,12 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\TagRepo; +use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasHtmlDescription; +use BookStack\Entities\Models\Page; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceStore; use BookStack\References\ReferenceUpdater; @@ -104,6 +107,33 @@ class BaseRepo } } + /** + * Update the default page template used for this item. + * Checks that, if changing, the provided value is a valid template and the user + * has visibility of the provided page template id. + */ + public function updateDefaultTemplate(Book|Chapter $entity, int $templateId): void + { + $changing = $templateId !== intval($entity->default_template_id); + if (!$changing) { + return; + } + + if ($templateId === 0) { + $entity->default_template_id = null; + $entity->save(); + return; + } + + $templateExists = Page::query()->visible() + ->where('template', '=', true) + ->where('id', '=', $templateId) + ->exists(); + + $entity->default_template_id = $templateExists ? $templateId : null; + $entity->save(); + } + protected function updateDescription(Entity $entity, array $input): void { if (!in_array(HasHtmlDescription::class, class_uses($entity))) { diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 03e1118b1..bf765b22d 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -86,7 +86,7 @@ class BookRepo $book = new Book(); $this->baseRepo->create($book, $input); $this->baseRepo->updateCoverImage($book, $input['image'] ?? null); - $this->updateBookDefaultTemplate($book, intval($input['default_template_id'] ?? null)); + $this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::BOOK_CREATE, $book); return $book; @@ -100,7 +100,7 @@ class BookRepo $this->baseRepo->update($book, $input); if (array_key_exists('default_template_id', $input)) { - $this->updateBookDefaultTemplate($book, intval($input['default_template_id'])); + $this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'])); } if (array_key_exists('image', $input)) { @@ -112,33 +112,6 @@ class BookRepo return $book; } - /** - * Update the default page template used for this book. - * Checks that, if changing, the provided value is a valid template and the user - * has visibility of the provided page template id. - */ - protected function updateBookDefaultTemplate(Book $book, int $templateId): void - { - $changing = $templateId !== intval($book->default_template_id); - if (!$changing) { - return; - } - - if ($templateId === 0) { - $book->default_template_id = null; - $book->save(); - return; - } - - $templateExists = Page::query()->visible() - ->where('template', '=', true) - ->where('id', '=', $templateId) - ->exists(); - - $book->default_template_id = $templateExists ? $templateId : null; - $book->save(); - } - /** * Update the given book's cover image, or clear it. * diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index 9534a4060..50b554d68 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -6,7 +6,6 @@ use BookStack\Activity\ActivityType; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Entity; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\TrashCan; use BookStack\Exceptions\MoveOperationException; @@ -47,7 +46,7 @@ class ChapterRepo $chapter->book_id = $parentBook->id; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; $this->baseRepo->create($chapter, $input); - $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'] ?? null)); + $this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::CHAPTER_CREATE, $chapter); return $chapter; @@ -61,7 +60,7 @@ class ChapterRepo $this->baseRepo->update($chapter, $input); if (array_key_exists('default_template_id', $input)) { - $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'])); + $this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'])); } Activity::add(ActivityType::CHAPTER_UPDATE, $chapter); @@ -108,33 +107,6 @@ class ChapterRepo return $parent; } - /** - * Update the default page template used for this chapter. - * Checks that, if changing, the provided value is a valid template and the user - * has visibility of the provided page template id. - */ - protected function updateChapterDefaultTemplate(Chapter $chapter, int $templateId): void - { - $changing = $templateId !== intval($chapter->default_template_id); - if (!$changing) { - return; - } - - if ($templateId === 0) { - $chapter->default_template_id = null; - $chapter->save(); - return; - } - - $templateExists = Page::query()->visible() - ->where('template', '=', true) - ->where('id', '=', $templateId) - ->exists(); - - $chapter->default_template_id = $templateExists ? $templateId : null; - $chapter->save(); - } - /** * Find a page parent entity via an identifier string in the format: * {type}:{id} diff --git a/tests/Entity/BookDefaultTemplateTest.php b/tests/Entity/BookDefaultTemplateTest.php deleted file mode 100644 index d4cd5b2c3..000000000 --- a/tests/Entity/BookDefaultTemplateTest.php +++ /dev/null @@ -1,185 +0,0 @@ -entities->templatePage(); - $details = [ - 'name' => 'My book with default template', - 'default_template_id' => $templatePage->id, - ]; - - $this->asEditor()->post('/books', $details); - $this->assertDatabaseHas('books', $details); - } - - public function test_updating_book_with_default_template() - { - $book = $this->entities->book(); - $templatePage = $this->entities->templatePage(); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => strval($templatePage->id)]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => '']); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); - } - - public function test_default_template_cannot_be_set_if_not_a_template() - { - $book = $this->entities->book(); - $page = $this->entities->page(); - $this->assertFalse($page->template); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $page->id]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); - } - - public function test_default_template_cannot_be_set_if_not_have_access() - { - $book = $this->entities->book(); - $templatePage = $this->entities->templatePage(); - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); - } - - public function test_inaccessible_default_template_can_be_set_if_unchanged() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); - } - - public function test_default_page_template_option_shows_on_book_form() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $resp = $this->asEditor()->get($book->getUrl('/edit')); - $this->withHtml($resp)->assertElementExists('input[name="default_template_id"][value="' . $templatePage->id . '"]'); - } - - public function test_default_page_template_option_only_shows_template_name_if_visible() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $resp = $this->asEditor()->get($book->getUrl('/edit')); - $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); - - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $resp = $this->asEditor()->get($book->getUrl('/edit')); - $this->withHtml($resp)->assertElementNotContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); - $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}"); - } - - public function test_creating_book_page_uses_default_template() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $this->asEditor()->get($book->getUrl('/create-page')); - $latestPage = $book->pages() - ->where('draft', '=', true) - ->where('template', '=', false) - ->latest()->first(); - - $this->assertEquals('

My template page

', $latestPage->html); - $this->assertEquals('# My template page', $latestPage->markdown); - } - - public function test_creating_chapter_page_uses_default_template() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page in chapter

', 'markdown' => '# My template page in chapter'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $chapter = $book->chapters()->first(); - - $this->asEditor()->get($chapter->getUrl('/create-page')); - $latestPage = $chapter->pages() - ->where('draft', '=', true) - ->where('template', '=', false) - ->latest()->first(); - - $this->assertEquals('

My template page in chapter

', $latestPage->html); - $this->assertEquals('# My template page in chapter', $latestPage->markdown); - } - - public function test_creating_book_page_as_guest_uses_default_template() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $guest = $this->users->guest(); - - $this->permissions->makeAppPublic(); - $this->permissions->grantUserRolePermissions($guest, ['page-create-all', 'page-update-all']); - - $resp = $this->post($book->getUrl('/create-guest-page'), [ - 'name' => 'My guest page with template' - ]); - $latestPage = $book->pages() - ->where('draft', '=', false) - ->where('template', '=', false) - ->where('created_by', '=', $guest->id) - ->latest()->first(); - - $this->assertEquals('

My template page

', $latestPage->html); - $this->assertEquals('# My template page', $latestPage->markdown); - } - - public function test_creating_book_page_does_not_use_template_if_not_visible() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $this->asEditor()->get($book->getUrl('/create-page')); - $latestPage = $book->pages() - ->where('draft', '=', true) - ->where('template', '=', false) - ->latest()->first(); - - $this->assertEquals('', $latestPage->html); - $this->assertEquals('', $latestPage->markdown); - } - - public function test_template_page_delete_removes_book_template_usage() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $book->refresh(); - $this->assertEquals($templatePage->id, $book->default_template_id); - - $this->asEditor()->delete($templatePage->getUrl()); - $this->asAdmin()->post('/settings/recycle-bin/empty'); - - $book->refresh(); - $this->assertEquals(null, $book->default_template_id); - } - - protected function bookUsingDefaultTemplate(Page $page): Book - { - $book = $this->entities->book(); - $book->default_template_id = $page->id; - $book->save(); - - return $book; - } -} diff --git a/tests/Entity/DefaultTemplateTest.php b/tests/Entity/DefaultTemplateTest.php new file mode 100644 index 000000000..5369a5430 --- /dev/null +++ b/tests/Entity/DefaultTemplateTest.php @@ -0,0 +1,341 @@ +entities->templatePage(); + $details = [ + 'name' => 'My book with default template', + 'default_template_id' => $templatePage->id, + ]; + + $this->asEditor()->post('/books', $details); + $this->assertDatabaseHas('books', $details); + } + + public function test_creating_chapter_with_default_template() + { + $templatePage = $this->entities->templatePage(); + $book = $this->entities->book(); + $details = [ + 'name' => 'My chapter with default template', + 'default_template_id' => $templatePage->id, + ]; + + $this->asEditor()->post($book->getUrl('/create-chapter'), $details); + $this->assertDatabaseHas('chapters', $details); + } + + public function test_updating_book_with_default_template() + { + $book = $this->entities->book(); + $templatePage = $this->entities->templatePage(); + + $this->asEditor()->put($book->getUrl(), ['name' => $book->name, 'default_template_id' => strval($templatePage->id)]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); + + $this->asEditor()->put($book->getUrl(), ['name' => $book->name, 'default_template_id' => '']); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); + } + + public function test_updating_chapter_with_default_template() + { + $chapter = $this->entities->chapter(); + $templatePage = $this->entities->templatePage(); + + $this->asEditor()->put($chapter->getUrl(), ['name' => $chapter->name, 'default_template_id' => strval($templatePage->id)]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => $templatePage->id]); + + $this->asEditor()->put($chapter->getUrl(), ['name' => $chapter->name, 'default_template_id' => '']); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => null]); + } + + public function test_default_book_template_cannot_be_set_if_not_a_template() + { + $book = $this->entities->book(); + $page = $this->entities->page(); + $this->assertFalse($page->template); + + $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $page->id]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); + } + + public function test_default_chapter_template_cannot_be_set_if_not_a_template() + { + $chapter = $this->entities->chapter(); + $page = $this->entities->page(); + $this->assertFalse($page->template); + + $this->asEditor()->put("/chapters/{$chapter->slug}", ['name' => $chapter->name, 'default_template_id' => $page->id]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => null]); + } + + + public function test_default_book_template_cannot_be_set_if_not_have_access() + { + $book = $this->entities->book(); + $templatePage = $this->entities->templatePage(); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); + } + + public function test_default_chapter_template_cannot_be_set_if_not_have_access() + { + $chapter = $this->entities->chapter(); + $templatePage = $this->entities->templatePage(); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/chapters/{$chapter->slug}", ['name' => $chapter->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => null]); + } + + public function test_inaccessible_book_default_template_can_be_set_if_unchanged() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); + } + + public function test_inaccessible_chapter_default_template_can_be_set_if_unchanged() + { + $templatePage = $this->entities->templatePage(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/chapters/{$chapter->slug}", ['name' => $chapter->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => $templatePage->id]); + } + + public function test_default_page_template_option_shows_on_book_form() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($book->getUrl('/edit')); + $this->withHtml($resp)->assertElementExists('input[name="default_template_id"][value="' . $templatePage->id . '"]'); + } + + public function test_default_page_template_option_shows_on_chapter_form() + { + $templatePage = $this->entities->templatePage(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($chapter->getUrl('/edit')); + $this->withHtml($resp)->assertElementExists('input[name="default_template_id"][value="' . $templatePage->id . '"]'); + } + + public function test_book_default_page_template_option_only_shows_template_name_if_visible() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($book->getUrl('/edit')); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $resp = $this->asEditor()->get($book->getUrl('/edit')); + $this->withHtml($resp)->assertElementNotContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}"); + } + + public function test_chapter_default_page_template_option_only_shows_template_name_if_visible() + { + $templatePage = $this->entities->templatePage(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($chapter->getUrl('/edit')); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $resp = $this->asEditor()->get($chapter->getUrl('/edit')); + $this->withHtml($resp)->assertElementNotContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}"); + } + + public function test_creating_book_page_uses_book_default_template() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + + $this->asEditor()->get($book->getUrl('/create-page')); + $latestPage = $book->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My template page

', $latestPage->html); + $this->assertEquals('# My template page', $latestPage->markdown); + } + + public function test_creating_chapter_page_uses_chapter_default_template() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My chapter template page

', 'markdown' => '# My chapter template page'])->save(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My chapter template page

', $latestPage->html); + $this->assertEquals('# My chapter template page', $latestPage->markdown); + } + + public function test_creating_chapter_page_uses_book_default_template_if_no_chapter_template_set() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page in chapter

', 'markdown' => '# My template page in chapter'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $book->chapters()->first(); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My template page in chapter

', $latestPage->html); + $this->assertEquals('# My template page in chapter', $latestPage->markdown); + } + + public function test_creating_chapter_page_uses_chapter_template_instead_of_book_template() + { + $bookTemplatePage = $this->entities->templatePage(); + $bookTemplatePage->forceFill(['html' => '

My book template

', 'markdown' => '# My book template'])->save(); + $book = $this->bookUsingDefaultTemplate($bookTemplatePage); + + $chapterTemplatePage = $this->entities->templatePage(); + $chapterTemplatePage->forceFill(['html' => '

My chapter template

', 'markdown' => '# My chapter template'])->save(); + $chapter = $book->chapters()->first(); + $chapter->default_template_id = $chapterTemplatePage->id; + $chapter->save(); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My chapter template

', $latestPage->html); + $this->assertEquals('# My chapter template', $latestPage->markdown); + } + + public function test_creating_page_as_guest_uses_default_template() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + $guest = $this->users->guest(); + + $this->permissions->makeAppPublic(); + $this->permissions->grantUserRolePermissions($guest, ['page-create-all', 'page-update-all']); + + $this->post($book->getUrl('/create-guest-page'), [ + 'name' => 'My guest page with template' + ]); + $latestBookPage = $book->pages() + ->where('draft', '=', false) + ->where('template', '=', false) + ->where('created_by', '=', $guest->id) + ->latest()->first(); + + $this->assertEquals('

My template page

', $latestBookPage->html); + $this->assertEquals('# My template page', $latestBookPage->markdown); + + $this->post($chapter->getUrl('/create-guest-page'), [ + 'name' => 'My guest page with template' + ]); + $latestChapterPage = $chapter->pages() + ->where('draft', '=', false) + ->where('template', '=', false) + ->where('created_by', '=', $guest->id) + ->latest()->first(); + + $this->assertEquals('

My template page

', $latestChapterPage->html); + $this->assertEquals('# My template page', $latestChapterPage->markdown); + } + + public function test_templates_not_used_if_not_visible() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->get($book->getUrl('/create-page')); + $latestBookPage = $book->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('', $latestBookPage->html); + $this->assertEquals('', $latestBookPage->markdown); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestChapterPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('', $latestChapterPage->html); + $this->assertEquals('', $latestChapterPage->markdown); + } + + public function test_template_page_delete_removes_template_usage() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $book->refresh(); + $this->assertEquals($templatePage->id, $book->default_template_id); + $this->assertEquals($templatePage->id, $chapter->default_template_id); + + $this->asEditor()->delete($templatePage->getUrl()); + $this->asAdmin()->post('/settings/recycle-bin/empty'); + + $book->refresh(); + $chapter->refresh(); + $this->assertEquals(null, $book->default_template_id); + $this->assertEquals(null, $chapter->default_template_id); + } + + protected function bookUsingDefaultTemplate(Page $page): Book + { + $book = $this->entities->book(); + $book->default_template_id = $page->id; + $book->save(); + + return $book; + } + + protected function chapterUsingDefaultTemplate(Page $page): Chapter + { + $chapter = $this->entities->chapter(); + $chapter->default_template_id = $page->id; + $chapter->save(); + + return $chapter; + } +}