Skip to content

Commit 4c7d642

Browse files
authored
DB: Aligned entity structure to a common table
As per PR #5800 * DB: Planned out new entity table format via migrations * DB: Created entity migration logic Made some other tweaks/fixes while testing. * DB: Added change of entity relation columns to suit new entities table * DB: Got most view queries working for new structure * Entities: Started logic change to new structure Updated base entity class, and worked through BaseRepo. Need to go through other repos next. Removed a couple of redundant interfaces as part of this since we can move the logic onto the shared ContainerData model as needed. * Entities: Been through repos to update for new format * Entities: Updated repos to act on refreshed clones Changes to core entity models are now done on clones to ensure clean state before save, and those clones are returned back if changes are needed after that action. * Entities: Updated model classes & relations for changes * Entities: Changed from *Data to a common "contents" system Added smart loading from builder instances which should hydrate with "contents()" loaded via join, while keeping the core model original. * Entities: Moved entity description/covers to own non-model classes Added back some interfaces. * Entities: Removed use of contents system for data access * Entities: Got most queries back to working order * Entities: Reverted back to data from contents, fixed various issues * Entities: Started addressing issues from tests * Entities: Addressed further tests/issues * Entities: Been through tests to get all passing in dev Fixed issues and needed test changes along the way. * Entities: Addressed phpstan errors * Entities: Reviewed TODO notes * Entities: Ensured book/shelf relation data removed on destroy * Entities: Been through API responses & adjusted field visibility * Entities: Added type index to massively improve query speed
1 parent 146a6c0 commit 4c7d642

File tree

120 files changed

+1598
-595
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+1598
-595
lines changed

app/Access/Mfa/MfaSession.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ class MfaSession
1111
*/
1212
public function isRequiredForUser(User $user): bool
1313
{
14-
// TODO - Test both these cases
1514
return $user->mfaValues()->exists() || $this->userRoleEnforcesMfa($user);
1615
}
1716

app/Console/Commands/UpdateUrlCommand.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,8 @@ public function handle(Connection $db): int
4545

4646
$columnsToUpdateByTable = [
4747
'attachments' => ['path'],
48-
'pages' => ['html', 'text', 'markdown'],
49-
'chapters' => ['description_html'],
50-
'books' => ['description_html'],
51-
'bookshelves' => ['description_html'],
48+
'entity_page_data' => ['html', 'text', 'markdown'],
49+
'entity_container_data' => ['description_html'],
5250
'page_revisions' => ['html', 'text', 'markdown'],
5351
'images' => ['url'],
5452
'settings' => ['value'],

app/Entities/Controllers/BookApiController.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ protected function forJsonDisplay(Book $book): Book
122122
$book = clone $book;
123123
$book->unsetRelations()->refresh();
124124

125-
$book->load(['tags', 'cover']);
126-
$book->makeVisible('description_html')
127-
->setAttribute('description_html', $book->descriptionHtml());
125+
$book->load(['tags']);
126+
$book->makeVisible(['cover', 'description_html'])
127+
->setAttribute('description_html', $book->descriptionInfo()->getHtml())
128+
->setAttribute('cover', $book->coverInfo()->getImage());
128129

129130
return $book;
130131
}

app/Entities/Controllers/BookshelfApiController.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@ protected function forJsonDisplay(Bookshelf $shelf): Bookshelf
116116
$shelf = clone $shelf;
117117
$shelf->unsetRelations()->refresh();
118118

119-
$shelf->load(['tags', 'cover']);
120-
$shelf->makeVisible('description_html')
121-
->setAttribute('description_html', $shelf->descriptionHtml());
119+
$shelf->load(['tags']);
120+
$shelf->makeVisible(['cover', 'description_html'])
121+
->setAttribute('description_html', $shelf->descriptionInfo()->getHtml())
122+
->setAttribute('cover', $shelf->coverInfo()->getImage());
122123

123124
return $shelf;
124125
}

app/Entities/Controllers/BookshelfController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public function show(Request $request, ActivityQueries $activities, string $slug
116116
]);
117117

118118
$sort = $listOptions->getSort();
119+
119120
$sortedVisibleShelfBooks = $shelf->visibleBooks()
120121
->reorder($sort === 'default' ? 'order' : $sort, $listOptions->getOrder())
121122
->get()

app/Entities/Controllers/ChapterApiController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public function update(Request $request, string $id)
104104
$chapter = $this->queries->findVisibleByIdOrFail(intval($id));
105105
$this->checkOwnablePermission(Permission::ChapterUpdate, $chapter);
106106

107-
if ($request->has('book_id') && $chapter->book_id !== intval($requestData['book_id'])) {
107+
if ($request->has('book_id') && $chapter->book_id !== (intval($requestData['book_id']) ?: null)) {
108108
$this->checkOwnablePermission(Permission::ChapterDelete, $chapter);
109109

110110
try {
@@ -144,7 +144,7 @@ protected function forJsonDisplay(Chapter $chapter): Chapter
144144

145145
$chapter->load(['tags']);
146146
$chapter->makeVisible('description_html');
147-
$chapter->setAttribute('description_html', $chapter->descriptionHtml());
147+
$chapter->setAttribute('description_html', $chapter->descriptionInfo()->getHtml());
148148

149149
/** @var Book $book */
150150
$book = $chapter->book()->first();

app/Entities/Controllers/ChapterController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public function update(Request $request, string $bookSlug, string $chapterSlug)
130130
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
131131
$this->checkOwnablePermission(Permission::ChapterUpdate, $chapter);
132132

133-
$this->chapterRepo->update($chapter, $validated);
133+
$chapter = $this->chapterRepo->update($chapter, $validated);
134134

135135
return redirect($chapter->getUrl());
136136
}

app/Entities/Controllers/PageController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public function store(Request $request, string $bookSlug, int $pageId)
120120
$this->validate($request, [
121121
'name' => ['required', 'string', 'max:255'],
122122
]);
123+
123124
$draftPage = $this->queries->findVisibleByIdOrFail($pageId);
124125
$this->checkOwnablePermission(Permission::PageCreate, $draftPage->getParent());
125126

app/Entities/EntityExistsRule.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace BookStack\Entities;
4+
5+
use Illuminate\Validation\Rules\Exists;
6+
7+
class EntityExistsRule implements \Stringable
8+
{
9+
public function __construct(
10+
protected string $type,
11+
) {
12+
}
13+
14+
public function __toString()
15+
{
16+
$existsRule = (new Exists('entities', 'id'))
17+
->where('type', $this->type);
18+
return $existsRule->__toString();
19+
}
20+
}

app/Entities/Models/Book.php

Lines changed: 33 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
namespace BookStack\Entities\Models;
44

5+
use BookStack\Entities\Tools\EntityCover;
6+
use BookStack\Entities\Tools\EntityDefaultTemplate;
57
use BookStack\Sorting\SortRule;
68
use BookStack\Uploads\Image;
7-
use Exception;
89
use Illuminate\Database\Eloquent\Factories\HasFactory;
910
use Illuminate\Database\Eloquent\Relations\BelongsTo;
1011
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -15,26 +16,25 @@
1516
* Class Book.
1617
*
1718
* @property string $description
19+
* @property string $description_html
1820
* @property int $image_id
1921
* @property ?int $default_template_id
2022
* @property ?int $sort_rule_id
21-
* @property Image|null $cover
2223
* @property \Illuminate\Database\Eloquent\Collection $chapters
2324
* @property \Illuminate\Database\Eloquent\Collection $pages
2425
* @property \Illuminate\Database\Eloquent\Collection $directPages
2526
* @property \Illuminate\Database\Eloquent\Collection $shelves
26-
* @property ?Page $defaultTemplate
27-
* @property ?SortRule $sortRule
27+
* @property ?SortRule $sortRule
2828
*/
29-
class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterface
29+
class Book extends Entity implements HasDescriptionInterface, HasCoverInterface, HasDefaultTemplateInterface
3030
{
3131
use HasFactory;
32-
use HtmlDescriptionTrait;
32+
use ContainerTrait;
3333

3434
public float $searchFactor = 1.2;
3535

36+
protected $hidden = ['pivot', 'deleted_at', 'description_html', 'entity_id', 'entity_type', 'chapter_id', 'book_id', 'priority'];
3637
protected $fillable = ['name'];
37-
protected $hidden = ['pivot', 'image_id', 'deleted_at', 'description_html'];
3838

3939
/**
4040
* Get the url for this book.
@@ -44,55 +44,6 @@ public function getUrl(string $path = ''): string
4444
return url('/books/' . implode('/', [urlencode($this->slug), trim($path, '/')]));
4545
}
4646

47-
/**
48-
* Returns book cover image, if book cover not exists return default cover image.
49-
*/
50-
public function getBookCover(int $width = 440, int $height = 250): string
51-
{
52-
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
53-
if (!$this->image_id || !$this->cover) {
54-
return $default;
55-
}
56-
57-
try {
58-
return $this->cover->getThumb($width, $height, false) ?? $default;
59-
} catch (Exception $err) {
60-
return $default;
61-
}
62-
}
63-
64-
/**
65-
* Get the cover image of the book.
66-
*/
67-
public function cover(): BelongsTo
68-
{
69-
return $this->belongsTo(Image::class, 'image_id');
70-
}
71-
72-
/**
73-
* Get the type of the image model that is used when storing a cover image.
74-
*/
75-
public function coverImageTypeKey(): string
76-
{
77-
return 'cover_book';
78-
}
79-
80-
/**
81-
* Get the Page that is used as default template for newly created pages within this Book.
82-
*/
83-
public function defaultTemplate(): BelongsTo
84-
{
85-
return $this->belongsTo(Page::class, 'default_template_id');
86-
}
87-
88-
/**
89-
* Get the sort set assigned to this book, if existing.
90-
*/
91-
public function sortRule(): BelongsTo
92-
{
93-
return $this->belongsTo(SortRule::class);
94-
}
95-
9647
/**
9748
* Get all pages within this book.
9849
* @return HasMany<Page, $this>
@@ -107,7 +58,7 @@ public function pages(): HasMany
10758
*/
10859
public function directPages(): HasMany
10960
{
110-
return $this->pages()->where('chapter_id', '=', '0');
61+
return $this->pages()->whereNull('chapter_id');
11162
}
11263

11364
/**
@@ -116,7 +67,8 @@ public function directPages(): HasMany
11667
*/
11768
public function chapters(): HasMany
11869
{
119-
return $this->hasMany(Chapter::class);
70+
return $this->hasMany(Chapter::class)
71+
->where('type', '=', 'chapter');
12072
}
12173

12274
/**
@@ -137,4 +89,27 @@ public function getDirectVisibleChildren(): Collection
13789

13890
return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
13991
}
92+
93+
public function defaultTemplate(): EntityDefaultTemplate
94+
{
95+
return new EntityDefaultTemplate($this);
96+
}
97+
98+
public function cover(): BelongsTo
99+
{
100+
return $this->belongsTo(Image::class, 'image_id');
101+
}
102+
103+
public function coverInfo(): EntityCover
104+
{
105+
return new EntityCover($this);
106+
}
107+
108+
/**
109+
* Get the sort rule assigned to this container, if existing.
110+
*/
111+
public function sortRule(): BelongsTo
112+
{
113+
return $this->belongsTo(SortRule::class);
114+
}
140115
}

0 commit comments

Comments
 (0)