Skip to content

Commit

Permalink
Merge pull request BookStackApp#4721 from BookStackApp/default-templates
Browse files Browse the repository at this point in the history
Continued: Default book templates
  • Loading branch information
ssddanbrown authored Dec 12, 2023
2 parents c13fd2a + 3af07ad commit 4896c40
Show file tree
Hide file tree
Showing 39 changed files with 497 additions and 76 deletions.
14 changes: 8 additions & 6 deletions app/Entities/Controllers/BookApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@

class BookApiController extends ApiController
{
protected BookRepo $bookRepo;

public function __construct(BookRepo $bookRepo)
{
$this->bookRepo = $bookRepo;
public function __construct(
protected BookRepo $bookRepo
) {
}

/**
Expand Down Expand Up @@ -58,7 +56,9 @@ public function create(Request $request)
*/
public function read(string $id)
{
$book = Book::visible()->with(['tags', 'cover', 'createdBy', 'updatedBy', 'ownedBy'])->findOrFail($id);
$book = Book::visible()
->with(['tags', 'cover', 'createdBy', 'updatedBy', 'ownedBy'])
->findOrFail($id);

$contents = (new BookContents($book))->getTree(true, false)->all();
$contentsApiData = (new ApiEntityListFormatter($contents))
Expand Down Expand Up @@ -116,12 +116,14 @@ protected function rules(): array
'description' => ['string', 'max:1000'],
'tags' => ['array'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
'default_template_id' => ['nullable', 'integer'],
],
'update' => [
'name' => ['string', 'min:1', 'max:255'],
'description' => ['string', 'max:1000'],
'tags' => ['array'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
'default_template_id' => ['nullable', 'integer'],
],
];
}
Expand Down
32 changes: 15 additions & 17 deletions app/Entities/Controllers/BookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@

class BookController extends Controller
{
protected BookRepo $bookRepo;
protected ShelfContext $shelfContext;
protected ReferenceFetcher $referenceFetcher;

public function __construct(ShelfContext $entityContextManager, BookRepo $bookRepo, ReferenceFetcher $referenceFetcher)
{
$this->bookRepo = $bookRepo;
$this->shelfContext = $entityContextManager;
$this->referenceFetcher = $referenceFetcher;
public function __construct(
protected ShelfContext $shelfContext,
protected BookRepo $bookRepo,
protected ReferenceFetcher $referenceFetcher
) {
}

/**
Expand Down Expand Up @@ -96,10 +92,11 @@ public function store(Request $request, string $shelfSlug = null)
{
$this->checkPermission('book-create-all');
$validated = $this->validate($request, [
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
'tags' => ['array'],
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
'tags' => ['array'],
'default_template_id' => ['nullable', 'integer'],
]);

$bookshelf = null;
Expand Down Expand Up @@ -170,10 +167,11 @@ public function update(Request $request, string $slug)
$this->checkOwnablePermission('book-update', $book);

$validated = $this->validate($request, [
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
'tags' => ['array'],
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'max:1000'],
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
'tags' => ['array'],
'default_template_id' => ['nullable', 'integer'],
]);

if ($request->has('image_reset')) {
Expand Down
6 changes: 5 additions & 1 deletion app/Entities/Controllers/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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\Page;
use BookStack\Entities\Repos\PageRepo;
use BookStack\Entities\Tools\BookContents;
Expand Down Expand Up @@ -71,7 +72,6 @@ public function createAsGuest(Request $request, string $bookSlug, string $chapte
$page = $this->pageRepo->getNewDraftPage($parent);
$this->pageRepo->publishDraft($page, [
'name' => $request->get('name'),
'html' => '',
]);

return redirect($page->getUrl('/edit'));
Expand Down Expand Up @@ -259,11 +259,13 @@ public function showDelete(string $bookSlug, string $pageSlug)
$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;

return view('pages.delete', [
'book' => $page->book,
'page' => $page,
'current' => $page,
'usedAsTemplate' => $usedAsTemplate,
]);
}

Expand All @@ -277,11 +279,13 @@ public function showDeleteDraft(string $bookSlug, int $pageId)
$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;

return view('pages.delete', [
'book' => $page->book,
'page' => $page,
'current' => $page,
'usedAsTemplate' => $usedAsTemplate,
]);
}

Expand Down
10 changes: 10 additions & 0 deletions app/Entities/Models/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*
* @property string $description
* @property int $image_id
* @property ?int $default_template_id
* @property Image|null $cover
* @property \Illuminate\Database\Eloquent\Collection $chapters
* @property \Illuminate\Database\Eloquent\Collection $pages
* @property \Illuminate\Database\Eloquent\Collection $directPages
* @property \Illuminate\Database\Eloquent\Collection $shelves
* @property ?Page $defaultTemplate
*/
class Book extends Entity implements HasCoverImage
{
Expand Down Expand Up @@ -71,6 +73,14 @@ public function coverImageTypeKey(): string
return 'cover_book';
}

/**
* Get the Page that is used as default template for newly created pages within this Book.
*/
public function defaultTemplate(): BelongsTo
{
return $this->belongsTo(Page::class, 'default_template_id');
}

/**
* Get all pages within this book.
*/
Expand Down
50 changes: 38 additions & 12 deletions app/Entities/Repos/BookRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use BookStack\Activity\ActivityType;
use BookStack\Activity\TagRepo;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
Expand All @@ -17,18 +18,11 @@

class BookRepo
{
protected $baseRepo;
protected $tagRepo;
protected $imageRepo;

/**
* BookRepo constructor.
*/
public function __construct(BaseRepo $baseRepo, TagRepo $tagRepo, ImageRepo $imageRepo)
{
$this->baseRepo = $baseRepo;
$this->tagRepo = $tagRepo;
$this->imageRepo = $imageRepo;
public function __construct(
protected BaseRepo $baseRepo,
protected TagRepo $tagRepo,
protected ImageRepo $imageRepo
) {
}

/**
Expand Down Expand Up @@ -92,6 +86,7 @@ public function create(array $input): Book
$book = new Book();
$this->baseRepo->create($book, $input);
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
$this->updateBookDefaultTemplate($book, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::BOOK_CREATE, $book);

return $book;
Expand All @@ -104,6 +99,10 @@ public function update(Book $book, array $input): Book
{
$this->baseRepo->update($book, $input);

if (array_key_exists('default_template_id', $input)) {
$this->updateBookDefaultTemplate($book, intval($input['default_template_id']));
}

if (array_key_exists('image', $input)) {
$this->baseRepo->updateCoverImage($book, $input['image'], $input['image'] === null);
}
Expand All @@ -113,6 +112,33 @@ public function update(Book $book, array $input): Book
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.
*
Expand Down
8 changes: 8 additions & 0 deletions app/Entities/Repos/PageRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ public function getNewDraftPage(Entity $parent)
$page->book_id = $parent->id;
}

$defaultTemplate = $page->book->defaultTemplate;
if ($defaultTemplate && userCan('view', $defaultTemplate)) {
$page->forceFill([
'html' => $defaultTemplate->html,
'markdown' => $defaultTemplate->markdown,
]);
}

$page->save();
$page->refresh()->rebuildPermissions();

Expand Down
4 changes: 4 additions & 0 deletions app/Entities/Tools/TrashCan.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ protected function destroyPage(Page $page): int
$attachmentService->deleteFile($attachment);
}

// Remove book template usages
Book::query()->where('default_template_id', '=', $page->id)
->update(['default_template_id' => null]);

$page->forceDelete();

return 1;
Expand Down
27 changes: 27 additions & 0 deletions app/Search/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace BookStack\Search;

use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\Popular;
use BookStack\Entities\Tools\SiblingFetcher;
use BookStack\Http\Controller;
Expand Down Expand Up @@ -82,6 +83,32 @@ public function searchForSelector(Request $request)
return view('search.parts.entity-selector-list', ['entities' => $entities, 'permission' => $permission]);
}

/**
* Search for a list of templates to choose from.
*/
public function templatesForSelector(Request $request)
{
$searchTerm = $request->get('term', false);

if ($searchTerm !== false) {
$searchOptions = SearchOptions::fromString($searchTerm);
$searchOptions->setFilter('is_template');
$entities = $this->searchRunner->searchEntities($searchOptions, 'page', 1, 20)['results'];
} else {
$entities = Page::visible()
->where('template', '=', true)
->where('draft', '=', false)
->orderBy('updated_at', 'desc')
->take(20)
->get(Page::$listAttributes);
}

return view('search.parts.entity-selector-list', [
'entities' => $entities,
'permission' => 'view'
]);
}

/**
* Search for a list of entities and return a partial HTML response of matching entities
* to be used as a result preview suggestion list for global system searches.
Expand Down
8 changes: 8 additions & 0 deletions app/Search/SearchOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ protected static function parseStandardTermString(string $termString): array
return $parsed;
}

/**
* Set the value of a specific filter in the search options.
*/
public function setFilter(string $filterName, string $filterValue = ''): void
{
$this->filters[$filterName] = $filterValue;
}

/**
* Encode this instance to a search string.
*/
Expand Down
9 changes: 8 additions & 1 deletion app/Search/SearchRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function searchEntities(SearchOptions $searchOpts, string $entityType = '
$entityTypesToSearch = $entityTypes;

if ($entityType !== 'all') {
$entityTypesToSearch = $entityType;
$entityTypesToSearch = [$entityType];
} elseif (isset($searchOpts->filters['type'])) {
$entityTypesToSearch = explode('|', $searchOpts->filters['type']);
}
Expand Down Expand Up @@ -469,6 +469,13 @@ protected function filterNotViewedByMe(EloquentBuilder $query, Entity $model, $i
});
}

protected function filterIsTemplate(EloquentBuilder $query, Entity $model, $input)
{
if ($model instanceof Page) {
$query->where('template', '=', true);
}
}

protected function filterSortBy(EloquentBuilder $query, Entity $model, $input)
{
$functionName = Str::camel('sort_by_' . $input);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddDefaultTemplateToBooks extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('books', function (Blueprint $table) {
$table->integer('default_template_id')->nullable()->default(null);
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('books', function (Blueprint $table) {
$table->dropColumn('default_template_id');
});
}
}
Loading

0 comments on commit 4896c40

Please sign in to comment.