namespace BookStack\Console\Commands;
-use BookStack\References\ReferenceService;
+use BookStack\References\ReferenceStore;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
*/
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();
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;
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;
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;
}
/**
$popular = $this->bookRepo->getPopular(4);
$new = $this->bookRepo->getRecentlyCreated(4);
- $this->entityContextManager->clearShelfContext();
+ $this->shelfContext->clearShelfContext();
$this->setPageTitle(trans('entities.books'));
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());
'bookChildren' => $bookChildren,
'bookParentShelves' => $bookParentShelves,
'activity' => $activities->entityActivity($book, 20, 1),
+ 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book),
]);
}
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;
{
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;
}
/**
'activity' => $activities->entityActivity($shelf, 20, 1),
'order' => $order,
'sort' => $sort,
+ 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf),
]);
}
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;
}
/**
$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),
]);
}
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;
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;
}
/**
'pageNav' => $pageNav,
'next' => $nextPreviousLocator->getNext(),
'previous' => $nextPreviousLocator->getPrevious(),
+ 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page),
]);
}
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;
}
/**
{
/** @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,
{
/** @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,
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,
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;
- }
}
--- /dev/null
+<?php
+
+namespace BookStack\References;
+
+use BookStack\Auth\Permissions\PermissionApplicator;
+use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Models\Page;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Relations\Relation;
+
+class ReferenceFetcher
+{
+ protected PermissionApplicator $permissions;
+
+ public function __construct(PermissionApplicator $permissions)
+ {
+ $this->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
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Collection;
-class ReferenceService
+class ReferenceStore
{
/**
'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',
<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
</div>
@endif
+
+ @if($referenceCount ?? 0)
+ <a href="{{ $entity->getUrl('/references') }}" class="entity-meta-item">
+ @icon('reference')
+ <div>
+ {!! trans_choice('entities.meta_reference_page_count', $referenceCount, ['count' => $referenceCount]) !!}
+ </div>
+ </a>
+ @endif
</div>
\ No newline at end of file
$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();