Merge branch 'chapter-templates' into development

This commit is contained in:
Dan Brown 2024-02-01 12:55:38 +00:00
commit 779f09bff6
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
23 changed files with 500 additions and 258 deletions

View File

@ -61,6 +61,8 @@ class ListingResponseBuilder
}
});
dd($data->first());
return response()->json([
'data' => $data,
'total' => $total,

View File

@ -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'],
],
];

View 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);

View 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,

View 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.
*/

View File

@ -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))) {

View File

@ -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.
*

View File

@ -4,8 +4,8 @@ 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;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\MoveOperationException;
@ -46,6 +46,7 @@ class ChapterRepo
$chapter->book_id = $parentBook->id;
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
$this->baseRepo->create($chapter, $input);
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
return $chapter;
@ -57,6 +58,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->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id']));
}
Activity::add(ActivityType::CHAPTER_UPDATE, $chapter);
return $chapter;

View File

@ -136,7 +136,7 @@ class PageRepo
$page->book_id = $parent->id;
}
$defaultTemplate = $page->book->defaultTemplate;
$defaultTemplate = $page->chapter->defaultTemplate ?? $page->book->defaultTemplate;
if ($defaultTemplate && userCan('view', $defaultTemplate)) {
$page->forceFill([
'html' => $defaultTemplate->html,

View File

@ -206,6 +206,10 @@ class TrashCan
Book::query()->where('default_template_id', '=', $page->id)
->update(['default_template_id' => null]);
// Remove chapter template usages
Chapter::query()->where('default_template_id', '=', $page->id)
->update(['default_template_id' => null]);
$page->forceDelete();
return 1;

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDefaultTemplateToChapters extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('chapters', function (Blueprint $table) {
$table->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');
});
}
}

View File

@ -3,6 +3,7 @@
"name": "My fantastic new chapter",
"description_html": "<p>This is a <strong>great new chapter</strong> that I've created via the API</p>",
"priority": 15,
"default_template_id": 25,
"tags": [
{"name": "Category", "value": "Top Content"},
{"name": "Rating", "value": "Highest"}

View File

@ -3,6 +3,7 @@
"name": "My fantastic updated chapter",
"description_html": "<p>This is an <strong>updated chapter</strong> that I've altered via the API</p>",
"priority": 16,
"default_template_id": 2428,
"tags": [
{"name": "Category", "value": "Kinda Good Content"},
{"name": "Rating", "value": "Medium"}

View File

@ -11,6 +11,7 @@
"updated_by": 1,
"owned_by": 1,
"description_html": "<p>This is a <strong>great new chapter<\/strong> that I've created via the API<\/p>",
"default_template_id": 25,
"book_slug": "example-book",
"tags": [
{

View File

@ -5,6 +5,7 @@
"name": "Content Creation",
"description": "How to create documentation on whatever subject you need to write about.",
"description_html": "<p>How to create <strong>documentation</strong> on whatever subject you need to write about.</p>",
"default_template_id": 25,
"priority": 3,
"created_at": "2019-05-05T21:49:56.000000Z",
"updated_at": "2019-09-28T11:24:23.000000Z",

View File

@ -11,6 +11,7 @@
"updated_by": 1,
"owned_by": 1,
"description_html": "<p>This is an <strong>updated chapter<\/strong> that I've altered via the API<\/p>",
"default_template_id": 2428,
"book_slug": "example-book",
"tags": [
{

View File

@ -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 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
'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.',
@ -207,7 +207,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',

View File

@ -40,24 +40,10 @@
<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.books_default_template') }}</label>
<label for="template-manager">{{ trans('entities.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.books_default_template_explain') }}
</p>
<div class="min-width-m">
@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',
])
</div>
</div>
@include('entities.template-selector', ['entity' => $book ?? null])
</div>
</div>

View File

@ -22,6 +22,15 @@
</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.default_template') }}</label>
</button>
<div refs="collapsible@content" class="collapse-content">
@include('entities.template-selector', ['entity' => $chapter ?? null])
</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>

View File

@ -0,0 +1,14 @@
<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.default_template_explain') }}
</p>
<div class="min-width-m">
@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',
])
</div>
</div>

View File

@ -36,6 +36,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',
@ -47,6 +48,7 @@ class ChaptersApiTest extends TestCase
],
],
'priority' => 15,
'default_template_id' => $templatePage->id,
];
$resp = $this->postJson($this->baseEndpoint, $details);
@ -149,6 +151,7 @@ class ChaptersApiTest extends TestCase
'name' => $page->name,
],
],
'default_template_id' => null,
]);
$resp->assertJsonMissingPath('book');
$resp->assertJsonCount($chapter->pages()->count(), 'pages');
@ -158,6 +161,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',
@ -168,6 +172,7 @@ class ChaptersApiTest extends TestCase
],
],
'priority' => 15,
'default_template_id' => $templatePage->id,
];
$resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);

View File

@ -1,185 +0,0 @@
<?php
namespace Tests\Entity;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use Tests\TestCase;
class BookDefaultTemplateTest extends TestCase
{
public function test_creating_book_with_default_template()
{
$templatePage = $this->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' => '<p>My template page</p>', '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('<p>My template page</p>', $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' => '<p>My template page in chapter</p>', '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('<p>My template page in chapter</p>', $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' => '<p>My template page</p>', '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('<p>My template page</p>', $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' => '<p>My template page</p>', '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;
}
}

View File

@ -0,0 +1,341 @@
<?php
namespace Tests\Entity;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use Tests\TestCase;
class DefaultTemplateTest extends TestCase
{
public function test_creating_book_with_default_template()
{
$templatePage = $this->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' => '<p>My template page</p>', '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('<p>My template page</p>', $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' => '<p>My chapter template page</p>', '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('<p>My chapter template page</p>', $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' => '<p>My template page in chapter</p>', '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('<p>My template page in chapter</p>', $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' => '<p>My book template</p>', 'markdown' => '# My book template'])->save();
$book = $this->bookUsingDefaultTemplate($bookTemplatePage);
$chapterTemplatePage = $this->entities->templatePage();
$chapterTemplatePage->forceFill(['html' => '<p>My chapter template</p>', '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('<p>My chapter template</p>', $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' => '<p>My template page</p>', '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('<p>My template page</p>', $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('<p>My template page</p>', $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' => '<p>My template page</p>', '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;
}
}