]> BookStack Code Mirror - bookstack/commitdiff
Added Default Templates for Chapters
authorSascha <redacted>
Mon, 1 Jan 2024 20:58:49 +0000 (21:58 +0100)
committerGitHub <redacted>
Mon, 1 Jan 2024 20:58:49 +0000 (21:58 +0100)
app/Entities/Controllers/ChapterController.php
app/Entities/Controllers/PageController.php
app/Entities/Models/Chapter.php
app/Entities/Repos/ChapterRepo.php
app/Entities/Repos/PageRepo.php
app/Entities/Tools/TrashCan.php
database/migrations/2024_01_01_104542_add_default_template_to_chapters.php [new file with mode: 0644]
lang/en/entities.php
resources/views/chapters/parts/form.blade.php

index 28ad35fa4b37e1b949ee942cd0f05475427baada..00616888a701c7aea91cc8f9f8517d9a3e1426c7 100644 (file)
@@ -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);
index adafcdc7bd919a26ca5a57c735f0f3440e1fcdbe..74dd4f531b43b9e182d4ce963651b5f513be5987 100644 (file)
@@ -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,
index f30d77b5c5c33d86d38554e431c4dbe64028d859..d3a7101116ba5e36341773b2190e08a899deba86 100644 (file)
@@ -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<Page> $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.
      */
index 977193d85bbd0229a2d1a1e745dbf5fd01dd4a83..9534a406082346307c7658db71c6c65ad73ab65e 100644 (file)
@@ -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}
index 7b14ea7d278c93b45bb0599251e8bbab6abfa337..67c4b222547c8b81143b5aaca5c4bb45ebcfa75b 100644 (file)
@@ -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,
index b25103985418f637604fb751a8d8b6868aacc409..e5bcfe71ac20f1e9678609070ea790df6baf15b8 100644 (file)
@@ -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 (file)
index 0000000..5e9ea1d
--- /dev/null
@@ -0,0 +1,32 @@
+<?php\r
+\r
+use Illuminate\Database\Migrations\Migration;\r
+use Illuminate\Database\Schema\Blueprint;\r
+use Illuminate\Support\Facades\Schema;\r
+\r
+class AddDefaultTemplateToChapters extends Migration\r
+{\r
+    /**\r
+     * Run the migrations.\r
+     *\r
+     * @return void\r
+     */\r
+    public function up()\r
+    {\r
+        Schema::table('chapters', function (Blueprint $table) {\r
+            $table->integer('default_template_id')->nullable()->default(null);\r
+        });\r
+    }\r
+\r
+    /**\r
+     * Reverse the migrations.\r
+     *\r
+     * @return void\r
+     */\r
+    public function down()\r
+    {\r
+        Schema::table('chapters', function (Blueprint $table) {\r
+            $table->dropColumn('default_template_id');\r
+        });\r
+    }\r
+}\r
index f1f915544d18db285e63110cb743a2004089e86c..4ab9de47dfb1cad4c55cb8325252ab9eb1d9b89d 100644 (file)
@@ -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',
index c6052c93af415b82d523f3163d3ceeaf0a45e108..ea7f84bc839e0ba2129c74739628217b00e17804 100644 (file)
     </div>
 </div>
 
+<div class="form-group collapsible" component="collapsible" id="template-control">
+    <button refs="collapsible@trigger" type="button" class="collapse-title text-link" aria-expanded="false">
+        <label for="template-manager">{{ trans('entities.chapter_default_template') }}</label>
+    </button>
+    <div refs="collapsible@content" class="collapse-content">
+        <div class="flex-container-row gap-l justify-space-between pb-xs wrap">
+            <p class="text-muted small my-none min-width-xs flex">
+                {{ trans('entities.chapter_default_template_explain') }}
+            </p>
+
+            <div class="min-width-m">
+                @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',
+                ])
+            </div>
+        </div>
+
+    </div>
+</div>
+
 <div class="form-group text-right">
     <a href="{{ isset($chapter) ? $chapter->getUrl() : $book->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
     <button type="submit" class="button">{{ trans('entities.chapters_save') }}</button>