1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-12-13 07:42:23 +03:00

Slugs: Added lookup system using history

Switched page lookup to use this.
This commit is contained in:
Dan Brown
2025-11-24 13:55:11 +00:00
parent dd5375f480
commit dd393691b1
4 changed files with 89 additions and 17 deletions

View File

@@ -17,7 +17,6 @@ use BookStack\Entities\Tools\PageContent;
use BookStack\Entities\Tools\PageEditActivity; use BookStack\Entities\Tools\PageEditActivity;
use BookStack\Entities\Tools\PageEditorData; use BookStack\Entities\Tools\PageEditorData;
use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\PermissionsException; use BookStack\Exceptions\PermissionsException;
use BookStack\Http\Controller; use BookStack\Http\Controller;
use BookStack\Permissions\Permission; use BookStack\Permissions\Permission;
@@ -140,9 +139,7 @@ class PageController extends Controller
try { try {
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
$revision = $this->entityQueries->revisions->findLatestVersionBySlugs($bookSlug, $pageSlug); $page = $this->entityQueries->findVisibleByOldSlugs('page', $pageSlug, $bookSlug);
$page = $revision->page ?? null;
if (is_null($page)) { if (is_null($page)) {
throw $e; throw $e;
} }

View File

@@ -0,0 +1,25 @@
<?php
namespace BookStack\Entities\Models;
use BookStack\App\Model;
use BookStack\Permissions\Models\JointPermission;
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
{
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');
}
}

View File

@@ -4,6 +4,7 @@ namespace BookStack\Entities\Queries;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\EntityTable; use BookStack\Entities\Models\EntityTable;
use BookStack\Entities\Tools\SlugHistory;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
@@ -18,6 +19,7 @@ class EntityQueries
public ChapterQueries $chapters, public ChapterQueries $chapters,
public PageQueries $pages, public PageQueries $pages,
public PageRevisionQueries $revisions, public PageRevisionQueries $revisions,
protected SlugHistory $slugHistory,
) { ) {
} }
@@ -31,9 +33,30 @@ class EntityQueries
$explodedId = explode(':', $identifier); $explodedId = explode(':', $identifier);
$entityType = $explodedId[0]; $entityType = $explodedId[0];
$entityId = intval($explodedId[1]); $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);
} }
/** /**

View File

@@ -4,10 +4,16 @@ namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use Illuminate\Support\Facades\DB; use BookStack\Entities\Models\SlugHistory as SlugHistoryModel;
use BookStack\Permissions\PermissionApplicator;
class SlugHistory class SlugHistory
{ {
public function __construct(
protected PermissionApplicator $permissions,
) {
}
/** /**
* Record the current slugs for the given entity. * Record the current slugs for the given entity.
*/ */
@@ -17,31 +23,52 @@ class SlugHistory
return; return;
} }
$latest = $this->getLatestEntryForEntity($entity);
if ($latest && $latest->slug === $entity->slug && $latest->parent_slug === $entity->getParent()?->slug) {
return;
}
$parentSlug = null; $parentSlug = null;
if ($entity instanceof BookChild) { if ($entity instanceof BookChild) {
$parentSlug = $entity->book()->first()?->slug; $parentSlug = $entity->book()->first()?->slug;
} }
$latest = $this->getLatestEntryForEntity($entity);
if ($latest && $latest->slug === $entity->slug && $latest->parent_slug === $parentSlug) {
return;
}
$info = [ $info = [
'sluggable_type' => $entity->getMorphClass(), 'sluggable_type' => $entity->getMorphClass(),
'sluggable_id' => $entity->id, 'sluggable_id' => $entity->id,
'slug' => $entity->slug, 'slug' => $entity->slug,
'parent_slug' => $parentSlug, 'parent_slug' => $parentSlug,
'created_at' => now(),
'updated_at' => now(),
]; ];
DB::table('slug_history')->insert($info); $entry = new SlugHistoryModel();
$entry->forceFill($info);
$entry->save();
} }
protected function getLatestEntryForEntity(Entity $entity): \stdClass|null /**
* 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
{ {
return DB::table('slug_history') $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): SlugHistoryModel|null
{
return SlugHistoryModel::query()
->where('sluggable_type', '=', $entity->getMorphClass()) ->where('sluggable_type', '=', $entity->getMorphClass())
->where('sluggable_id', '=', $entity->id) ->where('sluggable_id', '=', $entity->id)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')