mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Merge branch 'chapter-templates' into development
This commit is contained in:
commit
779f09bff6
@ -61,6 +61,8 @@ class ListingResponseBuilder
|
||||
}
|
||||
});
|
||||
|
||||
dd($data->first());
|
||||
|
||||
return response()->json([
|
||||
'data' => $data,
|
||||
'total' => $total,
|
||||
|
@ -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'],
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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))) {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
@ -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"}
|
||||
|
@ -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"}
|
||||
|
@ -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": [
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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": [
|
||||
{
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
14
resources/views/entities/template-selector.blade.php
Normal file
14
resources/views/entities/template-selector.blade.php
Normal 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>
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
341
tests/Entity/DefaultTemplateTest.php
Normal file
341
tests/Entity/DefaultTemplateTest.php
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user