]> BookStack Code Mirror - bookstack/blob - app/Entities/Tools/SlugHistory.php
Merge pull request #5913 from BookStackApp/slug_history
[bookstack] / app / Entities / Tools / SlugHistory.php
1 <?php
2
3 namespace BookStack\Entities\Tools;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\BookChild;
7 use BookStack\Entities\Models\Entity;
8 use BookStack\Entities\Models\EntityTable;
9 use BookStack\Entities\Models\SlugHistory as SlugHistoryModel;
10 use BookStack\Permissions\PermissionApplicator;
11 use Illuminate\Support\Facades\DB;
12
13 class SlugHistory
14 {
15     public function __construct(
16         protected PermissionApplicator $permissions,
17     ) {
18     }
19
20     /**
21      * Record the current slugs for the given entity.
22      */
23     public function recordForEntity(Entity $entity): void
24     {
25         if (!$entity->id || !$entity->slug) {
26             return;
27         }
28
29         $parentSlug = null;
30         if ($entity instanceof BookChild) {
31             $parentSlug = $entity->book()->first()?->slug;
32         }
33
34         $latest = $this->getLatestEntryForEntity($entity);
35         if ($latest && $latest->slug === $entity->slug && $latest->parent_slug === $parentSlug) {
36             return;
37         }
38
39         $info = [
40             'sluggable_type' => $entity->getMorphClass(),
41             'sluggable_id'   => $entity->id,
42             'slug'           => $entity->slug,
43             'parent_slug'    => $parentSlug,
44         ];
45
46         $entry = new SlugHistoryModel();
47         $entry->forceFill($info);
48         $entry->save();
49
50         if ($entity instanceof Book) {
51             $this->recordForBookChildren($entity);
52         }
53     }
54
55     protected function recordForBookChildren(Book $book): void
56     {
57         $query = EntityTable::query()
58             ->select(['type', 'id', 'slug', DB::raw("'{$book->slug}' as parent_slug"), DB::raw('now() as created_at'), DB::raw('now() as updated_at')])
59             ->where('book_id', '=', $book->id)
60             ->whereNotNull('book_id');
61
62         SlugHistoryModel::query()->insertUsing(
63             ['sluggable_type', 'sluggable_id', 'slug', 'parent_slug', 'created_at', 'updated_at'],
64             $query
65         );
66     }
67
68     /**
69      * Find the latest visible entry for an entity which uses the given slug(s) in the history.
70      */
71     public function lookupEntityIdUsingSlugs(string $type, string $slug, string $parentSlug = ''): ?int
72     {
73         $query = SlugHistoryModel::query()
74             ->where('sluggable_type', '=', $type)
75             ->where('slug', '=', $slug);
76
77         if ($parentSlug) {
78             $query->where('parent_slug', '=', $parentSlug);
79         }
80
81         $query = $this->permissions->restrictEntityRelationQuery($query, 'slug_history', 'sluggable_id', 'sluggable_type');
82
83         /** @var SlugHistoryModel|null $result */
84         $result = $query->orderBy('created_at', 'desc')->first();
85
86         return $result?->sluggable_id;
87     }
88
89     protected function getLatestEntryForEntity(Entity $entity): SlugHistoryModel|null
90     {
91         return SlugHistoryModel::query()
92             ->where('sluggable_type', '=', $entity->getMorphClass())
93             ->where('sluggable_id', '=', $entity->id)
94             ->orderBy('created_at', 'desc')
95             ->first();
96     }
97 }