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();