From: Dan Brown Date: Mon, 24 Nov 2025 13:55:11 +0000 (+0000) Subject: Slugs: Added lookup system using history X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/dd393691b17370f3967980b19daf95b206beac15 Slugs: Added lookup system using history Switched page lookup to use this. --- diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 603d015ef..a648bc298 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -17,7 +17,6 @@ use BookStack\Entities\Tools\PageContent; 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; @@ -140,9 +139,7 @@ class PageController extends Controller 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; } diff --git a/app/Entities/Models/SlugHistory.php b/app/Entities/Models/SlugHistory.php new file mode 100644 index 000000000..2731fe749 --- /dev/null +++ b/app/Entities/Models/SlugHistory.php @@ -0,0 +1,25 @@ +hasMany(JointPermission::class, 'entity_id', 'sluggable_id') + ->whereColumn('joint_permissions.entity_type', '=', 'slug_history.sluggable_type'); + } +} diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php index 91c6a4363..3ffa0adf3 100644 --- a/app/Entities/Queries/EntityQueries.php +++ b/app/Entities/Queries/EntityQueries.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Queries; 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; @@ -18,6 +19,7 @@ class EntityQueries public ChapterQueries $chapters, public PageQueries $pages, public PageRevisionQueries $revisions, + protected SlugHistory $slugHistory, ) { } @@ -31,9 +33,30 @@ class EntityQueries $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); } /** diff --git a/app/Entities/Tools/SlugHistory.php b/app/Entities/Tools/SlugHistory.php index dc4527a00..1584db9cf 100644 --- a/app/Entities/Tools/SlugHistory.php +++ b/app/Entities/Tools/SlugHistory.php @@ -4,10 +4,16 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Entity; -use Illuminate\Support\Facades\DB; +use BookStack\Entities\Models\SlugHistory as SlugHistoryModel; +use BookStack\Permissions\PermissionApplicator; class SlugHistory { + public function __construct( + protected PermissionApplicator $permissions, + ) { + } + /** * Record the current slugs for the given entity. */ @@ -17,31 +23,52 @@ class SlugHistory return; } - $latest = $this->getLatestEntryForEntity($entity); - if ($latest && $latest->slug === $entity->slug && $latest->parent_slug === $entity->getParent()?->slug) { - return; - } - $parentSlug = null; if ($entity instanceof BookChild) { $parentSlug = $entity->book()->first()?->slug; } + $latest = $this->getLatestEntryForEntity($entity); + if ($latest && $latest->slug === $entity->slug && $latest->parent_slug === $parentSlug) { + return; + } + $info = [ 'sluggable_type' => $entity->getMorphClass(), 'sluggable_id' => $entity->id, 'slug' => $entity->slug, 'parent_slug' => $parentSlug, - 'created_at' => now(), - 'updated_at' => now(), ]; - DB::table('slug_history')->insert($info); + $entry = new SlugHistoryModel(); + $entry->forceFill($info); + $entry->save(); + } + + /** + * Find the latest visible entry for an entity which uses the given slug(s) in the history. + */ + public function lookupEntityIdUsingSlugs(string $type, string $slug, string $parentSlug = ''): ?int + { + $query = SlugHistoryModel::query() + ->where('sluggable_type', '=', $type) + ->where('slug', '=', $slug); + + if ($parentSlug) { + $query->where('parent_slug', '=', $parentSlug); + } + + $query = $this->permissions->restrictEntityRelationQuery($query, 'slug_history', 'sluggable_id', 'sluggable_type'); + + /** @var SlugHistoryModel|null $result */ + $result = $query->orderBy('created_at', 'desc')->first(); + + return $result?->sluggable_id; } - protected function getLatestEntryForEntity(Entity $entity): \stdClass|null + protected function getLatestEntryForEntity(Entity $entity): SlugHistoryModel|null { - return DB::table('slug_history') + return SlugHistoryModel::query() ->where('sluggable_type', '=', $entity->getMorphClass()) ->where('sluggable_id', '=', $entity->id) ->orderBy('created_at', 'desc')