Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions app/App/SluggableInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
/**
* Assigned to models that can have slugs.
* Must have the below properties.
*
* @property string $slug
*/
interface SluggableInterface
{
/**
* Regenerate the slug for this model.
*/
public function refreshSlug(): string;
}
13 changes: 12 additions & 1 deletion app/Entities/Controllers/BookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use BookStack\Activity\Tools\UserEntityWatchOptions;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Queries\BookshelfQueries;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\Cloner;
Expand All @@ -31,6 +32,7 @@ public function __construct(
protected ShelfContext $shelfContext,
protected BookRepo $bookRepo,
protected BookQueries $queries,
protected EntityQueries $entityQueries,
protected BookshelfQueries $shelfQueries,
protected ReferenceFetcher $referenceFetcher,
) {
Expand Down Expand Up @@ -127,7 +129,16 @@ public function store(Request $request, ?string $shelfSlug = null)
*/
public function show(Request $request, ActivityQueries $activities, string $slug)
{
$book = $this->queries->findVisibleBySlugOrFail($slug);
try {
$book = $this->queries->findVisibleBySlugOrFail($slug);
} catch (NotFoundException $exception) {
$book = $this->entityQueries->findVisibleByOldSlugs('book', $slug);
if (is_null($book)) {
throw $exception;
}
return redirect($book->getUrl());
}

$bookChildren = (new BookContents($book))->getTree(true);
$bookParentShelves = $book->shelves()->scopes('visible')->get();

Expand Down
13 changes: 12 additions & 1 deletion app/Entities/Controllers/BookshelfController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use BookStack\Activity\Models\View;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Queries\BookshelfQueries;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Repos\BookshelfRepo;
use BookStack\Entities\Tools\ShelfContext;
use BookStack\Exceptions\ImageUploadException;
Expand All @@ -23,6 +24,7 @@ class BookshelfController extends Controller
public function __construct(
protected BookshelfRepo $shelfRepo,
protected BookshelfQueries $queries,
protected EntityQueries $entityQueries,
protected BookQueries $bookQueries,
protected ShelfContext $shelfContext,
protected ReferenceFetcher $referenceFetcher,
Expand Down Expand Up @@ -105,7 +107,16 @@ public function store(Request $request)
*/
public function show(Request $request, ActivityQueries $activities, string $slug)
{
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
try {
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
} catch (NotFoundException $exception) {
$shelf = $this->entityQueries->findVisibleByOldSlugs('bookshelf', $slug);
if (is_null($shelf)) {
throw $exception;
}
return redirect($shelf->getUrl());
}

$this->checkOwnablePermission(Permission::BookshelfView, $shelf);

$listOptions = SimpleListOptions::fromRequest($request, 'shelf_books')->withSortOptions([
Expand Down
10 changes: 9 additions & 1 deletion app/Entities/Controllers/ChapterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,15 @@ public function store(Request $request, string $bookSlug)
*/
public function show(string $bookSlug, string $chapterSlug)
{
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
try {
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
} catch (NotFoundException $exception) {
$chapter = $this->entityQueries->findVisibleByOldSlugs('chapter', $chapterSlug, $bookSlug);
if (is_null($chapter)) {
throw $exception;
}
return redirect($chapter->getUrl());
}

$sidebarTree = (new BookContents($chapter->book))->getTree();
$pages = $this->entityQueries->pages->visibleForChapterList($chapter->id)->get();
Expand Down
5 changes: 1 addition & 4 deletions app/Entities/Controllers/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use BookStack\Entities\Tools\PageEditActivity;
use BookStack\Entities\Tools\PageEditorData;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\PermissionsException;
use BookStack\Http\Controller;
use BookStack\Permissions\Permission;
Expand Down Expand Up @@ -140,9 +139,7 @@ public function show(string $bookSlug, string $pageSlug)
try {
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
} catch (NotFoundException $e) {
$revision = $this->entityQueries->revisions->findLatestVersionBySlugs($bookSlug, $pageSlug);
$page = $revision->page ?? null;

$page = $this->entityQueries->findVisibleByOldSlugs('page', $pageSlug, $bookSlug);
if (is_null($page)) {
throw $e;
}
Expand Down
27 changes: 1 addition & 26 deletions app/Entities/Models/BookChild.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace BookStack\Entities\Models;

use BookStack\References\ReferenceUpdater;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
Expand All @@ -17,34 +16,10 @@ abstract class BookChild extends Entity
{
/**
* Get the book this page sits in.
* @return BelongsTo<Book, $this>
*/
public function book(): BelongsTo
{
return $this->belongsTo(Book::class)->withTrashed();
}

/**
* Change the book that this entity belongs to.
*/
public function changeBook(int $newBookId): self
{
$oldUrl = $this->getUrl();
$this->book_id = $newBookId;
$this->unsetRelation('book');
$this->refreshSlug();
$this->save();

if ($oldUrl !== $this->getUrl()) {
app()->make(ReferenceUpdater::class)->updateEntityReferences($this, $oldUrl);
}

// Update all child pages if a chapter
if ($this instanceof Chapter) {
foreach ($this->pages()->withTrashed()->get() as $page) {
$page->changeBook($newBookId);
}
}

return $this;
}
}
19 changes: 8 additions & 11 deletions app/Entities/Models/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use BookStack\Activity\Models\Watch;
use BookStack\App\Model;
use BookStack\App\SluggableInterface;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Permissions\JointPermissionBuilder;
use BookStack\Permissions\Models\EntityPermission;
use BookStack\Permissions\Models\JointPermission;
Expand Down Expand Up @@ -405,16 +404,6 @@ public function indexForSearch(): void
app()->make(SearchIndex::class)->indexEntity(clone $this);
}

/**
* {@inheritdoc}
*/
public function refreshSlug(): string
{
$this->slug = app()->make(SlugGenerator::class)->generate($this, $this->name);

return $this->slug;
}

/**
* {@inheritdoc}
*/
Expand All @@ -441,6 +430,14 @@ public function watches(): MorphMany
return $this->morphMany(Watch::class, 'watchable');
}

/**
* Get the related slug history for this entity.
*/
public function slugHistory(): MorphMany
{
return $this->morphMany(SlugHistory::class, 'sluggable');
}

/**
* {@inheritdoc}
*/
Expand Down
28 changes: 28 additions & 0 deletions app/Entities/Models/SlugHistory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace BookStack\Entities\Models;

use BookStack\App\Model;
use BookStack\Permissions\Models\JointPermission;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
* @property int $id
* @property int $sluggable_id
* @property string $sluggable_type
* @property string $slug
* @property ?string $parent_slug
*/
class SlugHistory extends Model
{
use HasFactory;

protected $table = 'slug_history';

public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class, 'entity_id', 'sluggable_id')
->whereColumn('joint_permissions.entity_type', '=', 'slug_history.sluggable_type');
}
}
27 changes: 25 additions & 2 deletions app/Entities/Queries/EntityQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\EntityTable;
use BookStack\Entities\Tools\SlugHistory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\JoinClause;
Expand All @@ -18,6 +19,7 @@ public function __construct(
public ChapterQueries $chapters,
public PageQueries $pages,
public PageRevisionQueries $revisions,
protected SlugHistory $slugHistory,
) {
}

Expand All @@ -31,9 +33,30 @@ public function findVisibleByStringIdentifier(string $identifier): ?Entity
$explodedId = explode(':', $identifier);
$entityType = $explodedId[0];
$entityId = intval($explodedId[1]);
$queries = $this->getQueriesForType($entityType);

return $queries->findVisibleById($entityId);
return $this->findVisibleById($entityType, $entityId);
}

/**
* Find an entity by its ID.
*/
public function findVisibleById(string $type, int $id): ?Entity
{
$queries = $this->getQueriesForType($type);
return $queries->findVisibleById($id);
}

/**
* Find an entity by looking up old slugs in the slug history.
*/
public function findVisibleByOldSlugs(string $type, string $slug, string $parentSlug = ''): ?Entity
{
$id = $this->slugHistory->lookupEntityIdUsingSlugs($type, $slug, $parentSlug);
if ($id === null) {
return null;
}

return $this->findVisibleById($type, $id);
}

/**
Expand Down
17 changes: 15 additions & 2 deletions app/Entities/Repos/BaseRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use BookStack\Entities\Models\HasDescriptionInterface;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Entities\Tools\SlugHistory;
use BookStack\Exceptions\ImageUploadException;
use BookStack\References\ReferenceStore;
use BookStack\References\ReferenceUpdater;
Expand All @@ -25,6 +27,8 @@ public function __construct(
protected ReferenceStore $referenceStore,
protected PageQueries $pageQueries,
protected BookSorter $bookSorter,
protected SlugGenerator $slugGenerator,
protected SlugHistory $slugHistory,
) {
}

Expand All @@ -43,7 +47,7 @@ public function create(Entity $entity, array $input): Entity
'updated_by' => user()->id,
'owned_by' => user()->id,
]);
$entity->refreshSlug();
$this->refreshSlug($entity);

if ($entity instanceof HasDescriptionInterface) {
$this->updateDescription($entity, $input);
Expand Down Expand Up @@ -78,7 +82,7 @@ public function update(Entity $entity, array $input): Entity
$entity->updated_by = user()->id;

if ($entity->isDirty('name') || empty($entity->slug)) {
$entity->refreshSlug();
$this->refreshSlug($entity);
}

if ($entity instanceof HasDescriptionInterface) {
Expand Down Expand Up @@ -155,4 +159,13 @@ protected function updateDescription(Entity $entity, array $input): void
$entity->descriptionInfo()->set('', $input['description']);
}
}

/**
* Refresh the slug for the given entity.
*/
public function refreshSlug(Entity $entity): void
{
$this->slugHistory->recordForEntity($entity);
$this->slugGenerator->regenerateForEntity($entity);
}
}
4 changes: 3 additions & 1 deletion app/Entities/Repos/ChapterRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\ParentChanger;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\PermissionsException;
Expand All @@ -21,6 +22,7 @@ public function __construct(
protected BaseRepo $baseRepo,
protected EntityQueries $entityQueries,
protected TrashCan $trashCan,
protected ParentChanger $parentChanger,
) {
}

Expand Down Expand Up @@ -97,7 +99,7 @@ public function move(Chapter $chapter, string $parentIdentifier): Book
}

return (new DatabaseTransaction(function () use ($chapter, $parent) {
$chapter = $chapter->changeBook($parent->id);
$this->parentChanger->changeBook($chapter, $parent->id);
$chapter->rebuildPermissions();
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);

Expand Down
6 changes: 4 additions & 2 deletions app/Entities/Repos/PageRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\PageContent;
use BookStack\Entities\Tools\PageEditorType;
use BookStack\Entities\Tools\ParentChanger;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\PermissionsException;
Expand All @@ -31,6 +32,7 @@ public function __construct(
protected ReferenceStore $referenceStore,
protected ReferenceUpdater $referenceUpdater,
protected TrashCan $trashCan,
protected ParentChanger $parentChanger,
) {
}

Expand Down Expand Up @@ -242,7 +244,7 @@ public function restoreRevision(Page $page, int $revisionId): Page
}

$page->updated_by = user()->id;
$page->refreshSlug();
$this->baseRepo->refreshSlug($page);
$page->save();
$page->indexForSearch();
$this->referenceStore->updateForEntity($page);
Expand Down Expand Up @@ -284,7 +286,7 @@ public function move(Page $page, string $parentIdentifier): Entity
return (new DatabaseTransaction(function () use ($page, $parent) {
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
$page = $page->changeBook($newBookId);
$this->parentChanger->changeBook($page, $newBookId);
$page->rebuildPermissions();

Activity::add(ActivityType::PAGE_MOVE, $page);
Expand Down
Loading