From f634b4ea5767e6d823088c512872f9114322f7d0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 20 Aug 2022 12:07:38 +0100 Subject: [PATCH] Added entity meta link to reference page Not totally happy with implementation as is requires extra service to be injected to core controllers, but does the job. Included test to cover. Updated some controller properties to be typed while there. --- app/Console/Commands/RegenerateReferences.php | 6 +- app/Entities/Repos/PageRepo.php | 6 +- app/Http/Controllers/BookController.php | 16 +++-- app/Http/Controllers/BookshelfController.php | 6 +- app/Http/Controllers/ChapterController.php | 26 ++++---- app/Http/Controllers/PageController.php | 6 +- app/Http/Controllers/ReferenceController.php | 44 +++---------- app/References/ReferenceFetcher.php | 62 +++++++++++++++++++ ...eferenceService.php => ReferenceStore.php} | 2 +- resources/lang/en/entities.php | 1 + resources/views/entities/meta.blade.php | 9 +++ tests/References/ReferencesTest.php | 22 +++++++ 12 files changed, 143 insertions(+), 63 deletions(-) create mode 100644 app/References/ReferenceFetcher.php rename app/References/{ReferenceService.php => ReferenceStore.php} (98%) diff --git a/app/Console/Commands/RegenerateReferences.php b/app/Console/Commands/RegenerateReferences.php index 93450c5ea..805db2207 100644 --- a/app/Console/Commands/RegenerateReferences.php +++ b/app/Console/Commands/RegenerateReferences.php @@ -2,7 +2,7 @@ namespace BookStack\Console\Commands; -use BookStack\References\ReferenceService; +use BookStack\References\ReferenceStore; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -22,14 +22,14 @@ class RegenerateReferences extends Command */ protected $description = 'Regenerate all the cross-item model reference index'; - protected ReferenceService $references; + protected ReferenceStore $references; /** * Create a new command instance. * * @return void */ - public function __construct(ReferenceService $references) + public function __construct(ReferenceStore $references) { $this->references = $references; parent::__construct(); diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 09c664edc..40d1e6e53 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -16,7 +16,7 @@ use BookStack\Exceptions\MoveOperationException; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\PermissionsException; use BookStack\Facades\Activity; -use BookStack\References\ReferenceService; +use BookStack\References\ReferenceStore; use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator; @@ -24,12 +24,12 @@ use Illuminate\Pagination\LengthAwarePaginator; class PageRepo { protected BaseRepo $baseRepo; - protected ReferenceService $references; + protected ReferenceStore $references; /** * PageRepo constructor. */ - public function __construct(BaseRepo $baseRepo, ReferenceService $references) + public function __construct(BaseRepo $baseRepo, ReferenceStore $references) { $this->baseRepo = $baseRepo; $this->references = $references; diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index c5b6d0bf6..a041267bb 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -15,19 +15,22 @@ use BookStack\Entities\Tools\ShelfContext; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; use BookStack\Facades\Activity; +use BookStack\References\ReferenceFetcher; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; use Throwable; class BookController extends Controller { - protected $bookRepo; - protected $entityContextManager; + protected BookRepo $bookRepo; + protected ShelfContext $shelfContext; + protected ReferenceFetcher $referenceFetcher; - public function __construct(ShelfContext $entityContextManager, BookRepo $bookRepo) + public function __construct(ShelfContext $entityContextManager, BookRepo $bookRepo, ReferenceFetcher $referenceFetcher) { $this->bookRepo = $bookRepo; - $this->entityContextManager = $entityContextManager; + $this->shelfContext = $entityContextManager; + $this->referenceFetcher = $referenceFetcher; } /** @@ -44,7 +47,7 @@ class BookController extends Controller $popular = $this->bookRepo->getPopular(4); $new = $this->bookRepo->getRecentlyCreated(4); - $this->entityContextManager->clearShelfContext(); + $this->shelfContext->clearShelfContext(); $this->setPageTitle(trans('entities.books')); @@ -122,7 +125,7 @@ class BookController extends Controller View::incrementFor($book); if ($request->has('shelf')) { - $this->entityContextManager->setShelfContext(intval($request->get('shelf'))); + $this->shelfContext->setShelfContext(intval($request->get('shelf'))); } $this->setPageTitle($book->getShortName()); @@ -133,6 +136,7 @@ class BookController extends Controller 'bookChildren' => $bookChildren, 'bookParentShelves' => $bookParentShelves, 'activity' => $activities->entityActivity($book, 20, 1), + 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book), ]); } diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index ccbeb6484..2143b876a 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -10,6 +10,7 @@ use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Entities\Tools\ShelfContext; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; +use BookStack\References\ReferenceFetcher; use Exception; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; @@ -18,11 +19,13 @@ class BookshelfController extends Controller { protected BookshelfRepo $shelfRepo; protected ShelfContext $shelfContext; + protected ReferenceFetcher $referenceFetcher; - public function __construct(BookshelfRepo $shelfRepo, ShelfContext $shelfContext) + public function __construct(BookshelfRepo $shelfRepo, ShelfContext $shelfContext, ReferenceFetcher $referenceFetcher) { $this->shelfRepo = $shelfRepo; $this->shelfContext = $shelfContext; + $this->referenceFetcher = $referenceFetcher; } /** @@ -124,6 +127,7 @@ class BookshelfController extends Controller 'activity' => $activities->entityActivity($shelf, 20, 1), 'order' => $order, 'sort' => $sort, + 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf), ]); } diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 60eb52380..735c760be 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -13,20 +13,21 @@ use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Exceptions\MoveOperationException; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\PermissionsException; +use BookStack\References\ReferenceFetcher; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; use Throwable; class ChapterController extends Controller { - protected $chapterRepo; + protected ChapterRepo $chapterRepo; + protected ReferenceFetcher $referenceFetcher; - /** - * ChapterController constructor. - */ - public function __construct(ChapterRepo $chapterRepo) + + public function __construct(ChapterRepo $chapterRepo, ReferenceFetcher $referenceFetcher) { $this->chapterRepo = $chapterRepo; + $this->referenceFetcher = $referenceFetcher; } /** @@ -77,13 +78,14 @@ class ChapterController extends Controller $this->setPageTitle($chapter->getShortName()); return view('chapters.show', [ - 'book' => $chapter->book, - 'chapter' => $chapter, - 'current' => $chapter, - 'sidebarTree' => $sidebarTree, - 'pages' => $pages, - 'next' => $nextPreviousLocator->getNext(), - 'previous' => $nextPreviousLocator->getPrevious(), + 'book' => $chapter->book, + 'chapter' => $chapter, + 'current' => $chapter, + 'sidebarTree' => $sidebarTree, + 'pages' => $pages, + 'next' => $nextPreviousLocator->getNext(), + 'previous' => $nextPreviousLocator->getPrevious(), + 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($chapter), ]); } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index 268dce057..748468b21 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -14,6 +14,7 @@ use BookStack\Entities\Tools\PageEditorData; use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\PermissionsException; +use BookStack\References\ReferenceFetcher; use Exception; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Http\Request; @@ -23,13 +24,15 @@ use Throwable; class PageController extends Controller { protected PageRepo $pageRepo; + protected ReferenceFetcher $referenceFetcher; /** * PageController constructor. */ - public function __construct(PageRepo $pageRepo) + public function __construct(PageRepo $pageRepo, ReferenceFetcher $referenceFetcher) { $this->pageRepo = $pageRepo; + $this->referenceFetcher = $referenceFetcher; } /** @@ -160,6 +163,7 @@ class PageController extends Controller 'pageNav' => $pageNav, 'next' => $nextPreviousLocator->getNext(), 'previous' => $nextPreviousLocator->getPrevious(), + 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page), ]); } diff --git a/app/Http/Controllers/ReferenceController.php b/app/Http/Controllers/ReferenceController.php index 3af4feb06..07b143223 100644 --- a/app/Http/Controllers/ReferenceController.php +++ b/app/Http/Controllers/ReferenceController.php @@ -2,23 +2,19 @@ namespace BookStack\Http\Controllers; -use BookStack\Auth\Permissions\PermissionApplicator; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; -use Illuminate\Database\Eloquent\Collection; -use Illuminate\Database\Eloquent\Relations\Relation; +use BookStack\References\ReferenceFetcher; class ReferenceController extends Controller { + protected ReferenceFetcher $referenceFetcher; - protected PermissionApplicator $permissions; - - public function __construct(PermissionApplicator $permissions) + public function __construct(ReferenceFetcher $referenceFetcher) { - $this->permissions = $permissions; + $this->referenceFetcher = $referenceFetcher; } /** @@ -28,7 +24,7 @@ class ReferenceController extends Controller { /** @var Page $page */ $page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->firstOrFail(); - $references = $this->getEntityReferences($page); + $references = $this->referenceFetcher->getPageReferencesToEntity($page); return view('pages.references', [ 'page' => $page, @@ -43,7 +39,7 @@ class ReferenceController extends Controller { /** @var Chapter $chapter */ $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); - $references = $this->getEntityReferences($chapter); + $references = $this->referenceFetcher->getPageReferencesToEntity($chapter); return view('chapters.references', [ 'chapter' => $chapter, @@ -57,7 +53,7 @@ class ReferenceController extends Controller public function book(string $slug) { $book = Book::visible()->where('slug', '=', $slug)->firstOrFail(); - $references = $this->getEntityReferences($book); + $references = $this->referenceFetcher->getPageReferencesToEntity($book); return view('books.references', [ 'book' => $book, @@ -71,35 +67,11 @@ class ReferenceController extends Controller public function shelf(string $slug) { $shelf = Bookshelf::visible()->where('slug', '=', $slug)->firstOrFail(); - $references = $this->getEntityReferences($shelf); + $references = $this->referenceFetcher->getPageReferencesToEntity($shelf); return view('shelves.references', [ 'shelf' => $shelf, 'references' => $references, ]); } - - /** - * Query the references for the given entities. - * Loads the commonly required relations while taking permissions into account. - */ - protected function getEntityReferences(Entity $entity): Collection - { - $baseQuery = $entity->referencesTo() - ->where('from_type', '=', (new Page())->getMorphClass()) - ->with([ - 'from' => fn(Relation $query) => $query->select(Page::$listAttributes), - 'from.book' => fn(Relation $query) => $query->scopes('visible'), - 'from.chapter' => fn(Relation $query) => $query->scopes('visible') - ]); - - $references = $this->permissions->restrictEntityRelationQuery( - $baseQuery, - 'references', - 'from_id', - 'from_type' - )->get(); - - return $references; - } } diff --git a/app/References/ReferenceFetcher.php b/app/References/ReferenceFetcher.php new file mode 100644 index 000000000..fef2744d7 --- /dev/null +++ b/app/References/ReferenceFetcher.php @@ -0,0 +1,62 @@ +permissions = $permissions; + } + + /** + * Query and return the page references pointing to the given entity. + * Loads the commonly required relations while taking permissions into account. + */ + public function getPageReferencesToEntity(Entity $entity): Collection + { + $baseQuery = $entity->referencesTo() + ->where('from_type', '=', (new Page())->getMorphClass()) + ->with([ + 'from' => fn(Relation $query) => $query->select(Page::$listAttributes), + 'from.book' => fn(Relation $query) => $query->scopes('visible'), + 'from.chapter' => fn(Relation $query) => $query->scopes('visible') + ]); + + $references = $this->permissions->restrictEntityRelationQuery( + $baseQuery, + 'references', + 'from_id', + 'from_type' + )->get(); + + return $references; + } + + /** + * Returns the count of page references pointing to the given entity. + * Takes permissions into account. + */ + public function getPageReferenceCountToEntity(Entity $entity): int + { + $baseQuery = $entity->referencesTo() + ->where('from_type', '=', (new Page())->getMorphClass()); + + $count = $this->permissions->restrictEntityRelationQuery( + $baseQuery, + 'references', + 'from_id', + 'from_type' + )->count(); + + return $count; + } +} \ No newline at end of file diff --git a/app/References/ReferenceService.php b/app/References/ReferenceStore.php similarity index 98% rename from app/References/ReferenceService.php rename to app/References/ReferenceStore.php index fd7f74ae1..f6e3c04a3 100644 --- a/app/References/ReferenceService.php +++ b/app/References/ReferenceStore.php @@ -5,7 +5,7 @@ namespace BookStack\References; use BookStack\Entities\Models\Page; use Illuminate\Database\Eloquent\Collection; -class ReferenceService +class ReferenceStore { /** diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index a92b465b8..527665f88 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -23,6 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', + 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php index 83ff23762..ac91eeed3 100644 --- a/resources/views/entities/meta.blade.php +++ b/resources/views/entities/meta.blade.php @@ -59,4 +59,13 @@ {{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }} @endif + + @if($referenceCount ?? 0) + + @icon('reference') +
+ {!! trans_choice('entities.meta_reference_page_count', $referenceCount, ['count' => $referenceCount]) !!} +
+
+ @endif \ No newline at end of file diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php index 20829b6b4..9ae226bb7 100644 --- a/tests/References/ReferencesTest.php +++ b/tests/References/ReferencesTest.php @@ -54,6 +54,28 @@ class ReferencesTest extends TestCase $this->assertDatabaseMissing('references', ['to_id' => $pageA->id, 'to_type' => $pageA->getMorphClass()]); } + public function test_references_to_count_visible_on_entity_show_view() + { + $entities = $this->getEachEntityType(); + /** @var Page $otherPage */ + $otherPage = Page::query()->where('id', '!=', $entities['page']->id)->first(); + + $this->asEditor(); + foreach ($entities as $entity) { + $this->createReference($entities['page'], $entity); + } + + foreach ($entities as $entity) { + $resp = $this->get($entity->getUrl()); + $resp->assertSee('Referenced on 1 page'); + $resp->assertDontSee('Referenced on 1 pages'); + } + + $this->createReference($otherPage, $entities['page']); + $resp = $this->get($entities['page']->getUrl()); + $resp->assertSee('Referenced on 2 pages'); + } + public function test_references_to_visible_on_references_page() { $entities = $this->getEachEntityType();