diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 60f1d1b01..09c664edc 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -16,20 +16,23 @@ use BookStack\Exceptions\MoveOperationException; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\PermissionsException; use BookStack\Facades\Activity; +use BookStack\References\ReferenceService; use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator; class PageRepo { - protected $baseRepo; + protected BaseRepo $baseRepo; + protected ReferenceService $references; /** * PageRepo constructor. */ - public function __construct(BaseRepo $baseRepo) + public function __construct(BaseRepo $baseRepo, ReferenceService $references) { $this->baseRepo = $baseRepo; + $this->references = $references; } /** @@ -112,7 +115,7 @@ class PageRepo public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity { if ($chapterSlug !== null) { - return $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); + return Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); } return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); @@ -170,6 +173,7 @@ class PageRepo $this->savePageRevision($draft, trans('entities.pages_initial_revision')); $draft->indexForSearch(); + $this->references->updateForPage($draft); $draft->refresh(); Activity::add(ActivityType::PAGE_CREATE, $draft); @@ -189,6 +193,7 @@ class PageRepo $this->updateTemplateStatusAndContentFromInput($page, $input); $this->baseRepo->update($page, $input); + $this->references->updateForPage($page); // Update with new details $page->revision_count++; @@ -332,6 +337,7 @@ class PageRepo $page->refreshSlug(); $page->save(); $page->indexForSearch(); + $this->references->updateForPage($page); $summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]); $this->savePageRevision($page, $summary); @@ -430,6 +436,7 @@ class PageRepo ->skip(intval($revisionLimit)) ->take(10) ->get(['id']); + if ($revisionsToDelete->count() > 0) { PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete(); } diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index abec2e2d5..7341a0328 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -376,6 +376,8 @@ class TrashCan $entity->searchTerms()->delete(); $entity->deletions()->delete(); $entity->favourites()->delete(); + $entity->referencesTo()->delete(); + $entity->referencesFrom()->delete(); if ($entity instanceof HasCoverImage && $entity->cover()->exists()) { $imageService = app()->make(ImageService::class); diff --git a/app/References/Reference.php b/app/References/Reference.php index a2a7bda10..5a490b5b5 100644 --- a/app/References/Reference.php +++ b/app/References/Reference.php @@ -14,6 +14,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo; */ class Reference extends Model { + public $timestamps = false; + public function from(): MorphTo { return $this->morphTo('from'); diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php new file mode 100644 index 000000000..1285f5916 --- /dev/null +++ b/tests/References/ReferencesTest.php @@ -0,0 +1,67 @@ +<?php + +namespace Tests\References; + +use BookStack\Entities\Models\Page; +use BookStack\Entities\Repos\PageRepo; +use BookStack\Entities\Tools\TrashCan; +use BookStack\Model; +use BookStack\References\Reference; +use Tests\TestCase; + +class ReferencesTest extends TestCase +{ + + public function test_references_created_on_page_update() + { + /** @var Page $pageA */ + /** @var Page $pageB */ + $pageA = Page::query()->first(); + $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); + + $this->assertDatabaseMissing('references', ['from_id' => $pageA->id, 'from_type' => $pageA->getMorphClass()]); + + $this->asEditor()->put($pageA->getUrl(), [ + 'name' => 'Reference test', + 'html' => '<a href="' . $pageB->getUrl() . '">Testing</a>' + ]); + + $this->assertDatabaseHas('references', [ + 'from_id' => $pageA->id, + 'from_type' => $pageA->getMorphClass(), + 'to_id' => $pageB->id, + 'to_type' => $pageB->getMorphClass(), + ]); + } + + public function test_references_deleted_on_entity_delete() + { + /** @var Page $pageA */ + /** @var Page $pageB */ + $pageA = Page::query()->first(); + $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); + + $this->createReference($pageA, $pageB); + $this->createReference($pageB, $pageA); + + $this->assertDatabaseHas('references', ['from_id' => $pageA->id, 'from_type' => $pageA->getMorphClass()]); + $this->assertDatabaseHas('references', ['to_id' => $pageA->id, 'to_type' => $pageA->getMorphClass()]); + + app(PageRepo::class)->destroy($pageA); + app(TrashCan::class)->empty(); + + $this->assertDatabaseMissing('references', ['from_id' => $pageA->id, 'from_type' => $pageA->getMorphClass()]); + $this->assertDatabaseMissing('references', ['to_id' => $pageA->id, 'to_type' => $pageA->getMorphClass()]); + } + + protected function createReference(Model $from, Model $to) + { + (new Reference())->forceFill([ + 'from_type' => $from->getMorphClass(), + 'from_id' => $from->id, + 'to_type' => $to->getMorphClass(), + 'to_id' => $to->id, + ])->save(); + } + +} \ No newline at end of file