mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Input WYSIWYG: Added reference store & fetch handling
For book, shelves and chapters. Made much of the existing handling generic to entity types. Added new MixedEntityListLoader to help load lists somewhat efficiently. Only manually tested so far.
This commit is contained in:
parent
c622b785a9
commit
307fae39c4
@ -34,7 +34,7 @@ class RegenerateReferencesCommand extends Command
|
|||||||
DB::setDefaultConnection($this->option('database'));
|
DB::setDefaultConnection($this->option('database'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$references->updateForAllPages();
|
$references->updateForAll();
|
||||||
|
|
||||||
DB::setDefaultConnection($connection);
|
DB::setDefaultConnection($connection);
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ class BookController extends Controller
|
|||||||
'bookParentShelves' => $bookParentShelves,
|
'bookParentShelves' => $bookParentShelves,
|
||||||
'watchOptions' => new UserEntityWatchOptions(user(), $book),
|
'watchOptions' => new UserEntityWatchOptions(user(), $book),
|
||||||
'activity' => $activities->entityActivity($book, 20, 1),
|
'activity' => $activities->entityActivity($book, 20, 1),
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book),
|
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($book),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class BookshelfController extends Controller
|
|||||||
'view' => $view,
|
'view' => $view,
|
||||||
'activity' => $activities->entityActivity($shelf, 20, 1),
|
'activity' => $activities->entityActivity($shelf, 20, 1),
|
||||||
'listOptions' => $listOptions,
|
'listOptions' => $listOptions,
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf),
|
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($shelf),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ class ChapterController extends Controller
|
|||||||
'pages' => $pages,
|
'pages' => $pages,
|
||||||
'next' => $nextPreviousLocator->getNext(),
|
'next' => $nextPreviousLocator->getNext(),
|
||||||
'previous' => $nextPreviousLocator->getPrevious(),
|
'previous' => $nextPreviousLocator->getPrevious(),
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($chapter),
|
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($chapter),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ class PageController extends Controller
|
|||||||
'watchOptions' => new UserEntityWatchOptions(user(), $page),
|
'watchOptions' => new UserEntityWatchOptions(user(), $page),
|
||||||
'next' => $nextPreviousLocator->getNext(),
|
'next' => $nextPreviousLocator->getNext(),
|
||||||
'previous' => $nextPreviousLocator->getPrevious(),
|
'previous' => $nextPreviousLocator->getPrevious(),
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page),
|
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($page),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class Book extends Entity implements HasCoverImage
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
use HasHtmlDescription;
|
use HasHtmlDescription;
|
||||||
|
|
||||||
public $searchFactor = 1.2;
|
public float $searchFactor = 1.2;
|
||||||
|
|
||||||
protected $fillable = ['name'];
|
protected $fillable = ['name'];
|
||||||
protected $hidden = ['pivot', 'image_id', 'deleted_at'];
|
protected $hidden = ['pivot', 'image_id', 'deleted_at'];
|
||||||
|
@ -15,7 +15,7 @@ class Bookshelf extends Entity implements HasCoverImage
|
|||||||
|
|
||||||
protected $table = 'bookshelves';
|
protected $table = 'bookshelves';
|
||||||
|
|
||||||
public $searchFactor = 1.2;
|
public float $searchFactor = 1.2;
|
||||||
|
|
||||||
protected $fillable = ['name', 'description', 'image_id'];
|
protected $fillable = ['name', 'description', 'image_id'];
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class Chapter extends BookChild
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
use HasHtmlDescription;
|
use HasHtmlDescription;
|
||||||
|
|
||||||
public $searchFactor = 1.2;
|
public float $searchFactor = 1.2;
|
||||||
|
|
||||||
protected $fillable = ['name', 'description', 'priority'];
|
protected $fillable = ['name', 'description', 'priority'];
|
||||||
protected $hidden = ['pivot', 'deleted_at'];
|
protected $hidden = ['pivot', 'deleted_at'];
|
||||||
|
@ -57,12 +57,17 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
|||||||
/**
|
/**
|
||||||
* @var string - Name of property where the main text content is found
|
* @var string - Name of property where the main text content is found
|
||||||
*/
|
*/
|
||||||
public $textField = 'description';
|
public string $textField = 'description';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string - Name of the property where the main HTML content is found
|
||||||
|
*/
|
||||||
|
public string $htmlField = 'description_html';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var float - Multiplier for search indexing.
|
* @var float - Multiplier for search indexing.
|
||||||
*/
|
*/
|
||||||
public $searchFactor = 1.0;
|
public float $searchFactor = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the entities that are visible to the current user.
|
* Get the entities that are visible to the current user.
|
||||||
|
@ -37,7 +37,8 @@ class Page extends BookChild
|
|||||||
|
|
||||||
protected $fillable = ['name', 'priority'];
|
protected $fillable = ['name', 'priority'];
|
||||||
|
|
||||||
public $textField = 'text';
|
public string $textField = 'text';
|
||||||
|
public string $htmlField = 'html';
|
||||||
|
|
||||||
protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at'];
|
protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at'];
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use BookStack\Entities\Models\Entity;
|
|||||||
use BookStack\Entities\Models\HasCoverImage;
|
use BookStack\Entities\Models\HasCoverImage;
|
||||||
use BookStack\Entities\Models\HasHtmlDescription;
|
use BookStack\Entities\Models\HasHtmlDescription;
|
||||||
use BookStack\Exceptions\ImageUploadException;
|
use BookStack\Exceptions\ImageUploadException;
|
||||||
|
use BookStack\References\ReferenceStore;
|
||||||
use BookStack\References\ReferenceUpdater;
|
use BookStack\References\ReferenceUpdater;
|
||||||
use BookStack\Uploads\ImageRepo;
|
use BookStack\Uploads\ImageRepo;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
@ -16,7 +17,8 @@ class BaseRepo
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
protected TagRepo $tagRepo,
|
protected TagRepo $tagRepo,
|
||||||
protected ImageRepo $imageRepo,
|
protected ImageRepo $imageRepo,
|
||||||
protected ReferenceUpdater $referenceUpdater
|
protected ReferenceUpdater $referenceUpdater,
|
||||||
|
protected ReferenceStore $referenceStore,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ class BaseRepo
|
|||||||
$entity->refresh();
|
$entity->refresh();
|
||||||
$entity->rebuildPermissions();
|
$entity->rebuildPermissions();
|
||||||
$entity->indexForSearch();
|
$entity->indexForSearch();
|
||||||
|
$this->referenceStore->updateForEntity($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +71,7 @@ class BaseRepo
|
|||||||
|
|
||||||
$entity->rebuildPermissions();
|
$entity->rebuildPermissions();
|
||||||
$entity->indexForSearch();
|
$entity->indexForSearch();
|
||||||
|
$this->referenceStore->updateForEntity($entity);
|
||||||
|
|
||||||
if ($oldUrl !== $entity->getUrl()) {
|
if ($oldUrl !== $entity->getUrl()) {
|
||||||
$this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl);
|
$this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl);
|
||||||
|
@ -162,7 +162,6 @@ class PageRepo
|
|||||||
$this->baseRepo->update($draft, $input);
|
$this->baseRepo->update($draft, $input);
|
||||||
|
|
||||||
$this->revisionRepo->storeNewForPage($draft, trans('entities.pages_initial_revision'));
|
$this->revisionRepo->storeNewForPage($draft, trans('entities.pages_initial_revision'));
|
||||||
$this->referenceStore->updateForPage($draft);
|
|
||||||
$draft->refresh();
|
$draft->refresh();
|
||||||
|
|
||||||
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
||||||
@ -182,7 +181,6 @@ class PageRepo
|
|||||||
|
|
||||||
$this->updateTemplateStatusAndContentFromInput($page, $input);
|
$this->updateTemplateStatusAndContentFromInput($page, $input);
|
||||||
$this->baseRepo->update($page, $input);
|
$this->baseRepo->update($page, $input);
|
||||||
$this->referenceStore->updateForPage($page);
|
|
||||||
|
|
||||||
// Update with new details
|
// Update with new details
|
||||||
$page->revision_count++;
|
$page->revision_count++;
|
||||||
@ -301,7 +299,7 @@ class PageRepo
|
|||||||
$page->refreshSlug();
|
$page->refreshSlug();
|
||||||
$page->save();
|
$page->save();
|
||||||
$page->indexForSearch();
|
$page->indexForSearch();
|
||||||
$this->referenceStore->updateForPage($page);
|
$this->referenceStore->updateForEntity($page);
|
||||||
|
|
||||||
$summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]);
|
$summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]);
|
||||||
$this->revisionRepo->storeNewForPage($page, $summary);
|
$this->revisionRepo->storeNewForPage($page, $summary);
|
||||||
|
103
app/Entities/Tools/MixedEntityListLoader.php
Normal file
103
app/Entities/Tools/MixedEntityListLoader.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Entities\Tools;
|
||||||
|
|
||||||
|
use BookStack\App\Model;
|
||||||
|
use BookStack\Entities\EntityProvider;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
class MixedEntityListLoader
|
||||||
|
{
|
||||||
|
protected array $listAttributes = [
|
||||||
|
'page' => ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text', 'draft'],
|
||||||
|
'chapter' => ['id', 'name', 'slug', 'book_id', 'description'],
|
||||||
|
'book' => ['id', 'name', 'slug', 'description'],
|
||||||
|
'bookshelf' => ['id', 'name', 'slug', 'description'],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected EntityProvider $entityProvider
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Efficiently load in entities for listing onto the given list
|
||||||
|
* where entities are set as a relation via the given name.
|
||||||
|
* This will look for a model id and type via 'name_id' and 'name_type'.
|
||||||
|
* @param Model[] $relations
|
||||||
|
*/
|
||||||
|
public function loadIntoRelations(array $relations, string $relationName): void
|
||||||
|
{
|
||||||
|
$idsByType = [];
|
||||||
|
foreach ($relations as $relation) {
|
||||||
|
$type = $relation->getAttribute($relationName . '_type');
|
||||||
|
$id = $relation->getAttribute($relationName . '_id');
|
||||||
|
|
||||||
|
if (!isset($idsByType[$type])) {
|
||||||
|
$idsByType[$type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$idsByType[$type][] = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$modelMap = $this->idsByTypeToModelMap($idsByType);
|
||||||
|
|
||||||
|
foreach ($relations as $relation) {
|
||||||
|
$type = $relation->getAttribute($relationName . '_type');
|
||||||
|
$id = $relation->getAttribute($relationName . '_id');
|
||||||
|
$related = $modelMap[$type][strval($id)] ?? null;
|
||||||
|
if ($related) {
|
||||||
|
$relation->setRelation($relationName, $related);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, int[]> $idsByType
|
||||||
|
* @return array<string, array<int, Model>>
|
||||||
|
*/
|
||||||
|
protected function idsByTypeToModelMap(array $idsByType): array
|
||||||
|
{
|
||||||
|
$modelMap = [];
|
||||||
|
|
||||||
|
foreach ($idsByType as $type => $ids) {
|
||||||
|
if (!isset($this->listAttributes[$type])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance = $this->entityProvider->get($type);
|
||||||
|
$models = $instance->newQuery()
|
||||||
|
->select($this->listAttributes[$type])
|
||||||
|
->scopes('visible')
|
||||||
|
->whereIn('id', $ids)
|
||||||
|
->with($this->getRelationsToEagerLoad($type))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if (count($models) > 0) {
|
||||||
|
$modelMap[$type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$modelMap[$type][strval($model->id)] = $model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $modelMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRelationsToEagerLoad(string $type): array
|
||||||
|
{
|
||||||
|
$toLoad = [];
|
||||||
|
$loadVisible = fn (Relation $query) => $query->scopes('visible');
|
||||||
|
|
||||||
|
if ($type === 'chapter' || $type === 'page') {
|
||||||
|
$toLoad['book'] = $loadVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'page') {
|
||||||
|
$toLoad['chapter'] = $loadVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $toLoad;
|
||||||
|
}
|
||||||
|
}
|
@ -10,11 +10,9 @@ use BookStack\Http\Controller;
|
|||||||
|
|
||||||
class ReferenceController extends Controller
|
class ReferenceController extends Controller
|
||||||
{
|
{
|
||||||
protected ReferenceFetcher $referenceFetcher;
|
public function __construct(
|
||||||
|
protected ReferenceFetcher $referenceFetcher
|
||||||
public function __construct(ReferenceFetcher $referenceFetcher)
|
) {
|
||||||
{
|
|
||||||
$this->referenceFetcher = $referenceFetcher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +21,7 @@ class ReferenceController extends Controller
|
|||||||
public function page(string $bookSlug, string $pageSlug)
|
public function page(string $bookSlug, string $pageSlug)
|
||||||
{
|
{
|
||||||
$page = Page::getBySlugs($bookSlug, $pageSlug);
|
$page = Page::getBySlugs($bookSlug, $pageSlug);
|
||||||
$references = $this->referenceFetcher->getPageReferencesToEntity($page);
|
$references = $this->referenceFetcher->getReferencesToEntity($page);
|
||||||
|
|
||||||
return view('pages.references', [
|
return view('pages.references', [
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
@ -37,7 +35,7 @@ class ReferenceController extends Controller
|
|||||||
public function chapter(string $bookSlug, string $chapterSlug)
|
public function chapter(string $bookSlug, string $chapterSlug)
|
||||||
{
|
{
|
||||||
$chapter = Chapter::getBySlugs($bookSlug, $chapterSlug);
|
$chapter = Chapter::getBySlugs($bookSlug, $chapterSlug);
|
||||||
$references = $this->referenceFetcher->getPageReferencesToEntity($chapter);
|
$references = $this->referenceFetcher->getReferencesToEntity($chapter);
|
||||||
|
|
||||||
return view('chapters.references', [
|
return view('chapters.references', [
|
||||||
'chapter' => $chapter,
|
'chapter' => $chapter,
|
||||||
@ -51,7 +49,7 @@ class ReferenceController extends Controller
|
|||||||
public function book(string $slug)
|
public function book(string $slug)
|
||||||
{
|
{
|
||||||
$book = Book::getBySlug($slug);
|
$book = Book::getBySlug($slug);
|
||||||
$references = $this->referenceFetcher->getPageReferencesToEntity($book);
|
$references = $this->referenceFetcher->getReferencesToEntity($book);
|
||||||
|
|
||||||
return view('books.references', [
|
return view('books.references', [
|
||||||
'book' => $book,
|
'book' => $book,
|
||||||
@ -65,7 +63,7 @@ class ReferenceController extends Controller
|
|||||||
public function shelf(string $slug)
|
public function shelf(string $slug)
|
||||||
{
|
{
|
||||||
$shelf = Bookshelf::getBySlug($slug);
|
$shelf = Bookshelf::getBySlug($slug);
|
||||||
$references = $this->referenceFetcher->getPageReferencesToEntity($shelf);
|
$references = $this->referenceFetcher->getReferencesToEntity($shelf);
|
||||||
|
|
||||||
return view('shelves.references', [
|
return view('shelves.references', [
|
||||||
'shelf' => $shelf,
|
'shelf' => $shelf,
|
||||||
|
@ -3,65 +3,51 @@
|
|||||||
namespace BookStack\References;
|
namespace BookStack\References;
|
||||||
|
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||||
use BookStack\Permissions\PermissionApplicator;
|
use BookStack\Permissions\PermissionApplicator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
||||||
|
|
||||||
class ReferenceFetcher
|
class ReferenceFetcher
|
||||||
{
|
{
|
||||||
protected PermissionApplicator $permissions;
|
public function __construct(
|
||||||
|
protected PermissionApplicator $permissions,
|
||||||
public function __construct(PermissionApplicator $permissions)
|
protected MixedEntityListLoader $mixedEntityListLoader,
|
||||||
{
|
) {
|
||||||
$this->permissions = $permissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query and return the page references pointing to the given entity.
|
* Query and return the references pointing to the given entity.
|
||||||
* Loads the commonly required relations while taking permissions into account.
|
* Loads the commonly required relations while taking permissions into account.
|
||||||
*/
|
*/
|
||||||
public function getPageReferencesToEntity(Entity $entity): Collection
|
public function getReferencesToEntity(Entity $entity): Collection
|
||||||
{
|
{
|
||||||
$baseQuery = $this->queryPageReferencesToEntity($entity)
|
$references = $this->queryReferencesToEntity($entity)->get();
|
||||||
->with([
|
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from');
|
||||||
'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;
|
return $references;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the count of page references pointing to the given entity.
|
* Returns the count of references pointing to the given entity.
|
||||||
* Takes permissions into account.
|
* Takes permissions into account.
|
||||||
*/
|
*/
|
||||||
public function getPageReferenceCountToEntity(Entity $entity): int
|
public function getReferenceCountToEntity(Entity $entity): int
|
||||||
{
|
{
|
||||||
$count = $this->permissions->restrictEntityRelationQuery(
|
return $this->queryReferencesToEntity($entity)->count();
|
||||||
$this->queryPageReferencesToEntity($entity),
|
}
|
||||||
|
|
||||||
|
protected function queryReferencesToEntity(Entity $entity): Builder
|
||||||
|
{
|
||||||
|
$baseQuery = Reference::query()
|
||||||
|
->where('to_type', '=', $entity->getMorphClass())
|
||||||
|
->where('to_id', '=', $entity->id);
|
||||||
|
|
||||||
|
return $this->permissions->restrictEntityRelationQuery(
|
||||||
|
$baseQuery,
|
||||||
'references',
|
'references',
|
||||||
'from_id',
|
'from_id',
|
||||||
'from_type'
|
'from_type'
|
||||||
)->count();
|
);
|
||||||
|
|
||||||
return $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function queryPageReferencesToEntity(Entity $entity): Builder
|
|
||||||
{
|
|
||||||
return Reference::query()
|
|
||||||
->where('to_type', '=', $entity->getMorphClass())
|
|
||||||
->where('to_id', '=', $entity->id)
|
|
||||||
->where('from_type', '=', (new Page())->getMorphClass());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,60 +2,62 @@
|
|||||||
|
|
||||||
namespace BookStack\References;
|
namespace BookStack\References;
|
||||||
|
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\EntityProvider;
|
||||||
|
use BookStack\Entities\Models\Entity;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
class ReferenceStore
|
class ReferenceStore
|
||||||
{
|
{
|
||||||
/**
|
public function __construct(
|
||||||
* Update the outgoing references for the given page.
|
protected EntityProvider $entityProvider
|
||||||
*/
|
) {
|
||||||
public function updateForPage(Page $page): void
|
|
||||||
{
|
|
||||||
$this->updateForPages([$page]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the outgoing references for all pages in the system.
|
* Update the outgoing references for the given entity.
|
||||||
*/
|
*/
|
||||||
public function updateForAllPages(): void
|
public function updateForEntity(Entity $entity): void
|
||||||
{
|
{
|
||||||
Reference::query()
|
$this->updateForEntities([$entity]);
|
||||||
->where('from_type', '=', (new Page())->getMorphClass())
|
}
|
||||||
->delete();
|
|
||||||
|
|
||||||
Page::query()->select(['id', 'html'])->chunk(100, function (Collection $pages) {
|
/**
|
||||||
$this->updateForPages($pages->all());
|
* Update the outgoing references for all entities in the system.
|
||||||
|
*/
|
||||||
|
public function updateForAll(): void
|
||||||
|
{
|
||||||
|
Reference::query()->delete();
|
||||||
|
|
||||||
|
foreach ($this->entityProvider->all() as $entity) {
|
||||||
|
$entity->newQuery()->select(['id', $entity->htmlField])->chunk(100, function (Collection $entities) {
|
||||||
|
$this->updateForEntities($entities->all());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the outgoing references for the pages in the given array.
|
* Update the outgoing references for the entities in the given array.
|
||||||
*
|
*
|
||||||
* @param Page[] $pages
|
* @param Entity[] $entities
|
||||||
*/
|
*/
|
||||||
protected function updateForPages(array $pages): void
|
protected function updateForEntities(array $entities): void
|
||||||
{
|
{
|
||||||
if (count($pages) === 0) {
|
if (count($entities) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$parser = CrossLinkParser::createWithEntityResolvers();
|
$parser = CrossLinkParser::createWithEntityResolvers();
|
||||||
$references = [];
|
$references = [];
|
||||||
|
|
||||||
$pageIds = array_map(fn (Page $page) => $page->id, $pages);
|
$this->dropReferencesFromEntities($entities);
|
||||||
Reference::query()
|
|
||||||
->where('from_type', '=', $pages[0]->getMorphClass())
|
|
||||||
->whereIn('from_id', $pageIds)
|
|
||||||
->delete();
|
|
||||||
|
|
||||||
foreach ($pages as $page) {
|
foreach ($entities as $entity) {
|
||||||
$models = $parser->extractLinkedModels($page->html);
|
$models = $parser->extractLinkedModels($entity->getAttribute($entity->htmlField));
|
||||||
|
|
||||||
foreach ($models as $model) {
|
foreach ($models as $model) {
|
||||||
$references[] = [
|
$references[] = [
|
||||||
'from_id' => $page->id,
|
'from_id' => $entity->id,
|
||||||
'from_type' => $page->getMorphClass(),
|
'from_type' => $entity->getMorphClass(),
|
||||||
'to_id' => $model->id,
|
'to_id' => $model->id,
|
||||||
'to_type' => $model->getMorphClass(),
|
'to_type' => $model->getMorphClass(),
|
||||||
];
|
];
|
||||||
@ -66,4 +68,29 @@ class ReferenceStore
|
|||||||
Reference::query()->insert($referenceDataChunk);
|
Reference::query()->insert($referenceDataChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all the existing references originating from the given entities.
|
||||||
|
* @param Entity[] $entities
|
||||||
|
*/
|
||||||
|
protected function dropReferencesFromEntities(array $entities): void
|
||||||
|
{
|
||||||
|
$IdsByType = [];
|
||||||
|
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$type = $entity->getMorphClass();
|
||||||
|
if (!isset($IdsByType[$type])) {
|
||||||
|
$IdsByType[$type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$IdsByType[$type][] = $entity->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($IdsByType as $type => $entityIds) {
|
||||||
|
Reference::query()
|
||||||
|
->where('from_type', '=', $type)
|
||||||
|
->whereIn('from_id', $entityIds)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class ReferenceUpdater
|
|||||||
protected function getReferencesToUpdate(Entity $entity): array
|
protected function getReferencesToUpdate(Entity $entity): array
|
||||||
{
|
{
|
||||||
/** @var Reference[] $references */
|
/** @var Reference[] $references */
|
||||||
$references = $this->referenceFetcher->getPageReferencesToEntity($entity)->values()->all();
|
$references = $this->referenceFetcher->getReferencesToEntity($entity)->values()->all();
|
||||||
|
|
||||||
if ($entity instanceof Book) {
|
if ($entity instanceof Book) {
|
||||||
$pages = $entity->pages()->get(['id']);
|
$pages = $entity->pages()->get(['id']);
|
||||||
@ -43,7 +43,7 @@ class ReferenceUpdater
|
|||||||
$children = $pages->concat($chapters);
|
$children = $pages->concat($chapters);
|
||||||
foreach ($children as $bookChild) {
|
foreach ($children as $bookChild) {
|
||||||
/** @var Reference[] $childRefs */
|
/** @var Reference[] $childRefs */
|
||||||
$childRefs = $this->referenceFetcher->getPageReferencesToEntity($bookChild)->values()->all();
|
$childRefs = $this->referenceFetcher->getReferencesToEntity($bookChild)->values()->all();
|
||||||
array_push($references, ...$childRefs);
|
array_push($references, ...$childRefs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ class MaintenanceController extends Controller
|
|||||||
$this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'regenerate-references');
|
$this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'regenerate-references');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$referenceStore->updateForAllPages();
|
$referenceStore->updateForAll();
|
||||||
$this->showSuccessNotification(trans('settings.maint_regen_references_success'));
|
$this->showSuccessNotification(trans('settings.maint_regen_references_success'));
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
$this->showErrorNotification($exception->getMessage());
|
$this->showErrorNotification($exception->getMessage());
|
||||||
|
@ -23,7 +23,7 @@ return [
|
|||||||
'meta_updated' => 'Updated :timeLength',
|
'meta_updated' => 'Updated :timeLength',
|
||||||
'meta_updated_name' => 'Updated :timeLength by :user',
|
'meta_updated_name' => 'Updated :timeLength by :user',
|
||||||
'meta_owned_name' => 'Owned by :user',
|
'meta_owned_name' => 'Owned by :user',
|
||||||
'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages',
|
'meta_reference_count' => 'Referenced by :count item|Referenced by :count items',
|
||||||
'entity_select' => 'Entity Select',
|
'entity_select' => 'Entity Select',
|
||||||
'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item',
|
'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item',
|
||||||
'images' => 'Images',
|
'images' => 'Images',
|
||||||
@ -409,7 +409,7 @@ return [
|
|||||||
// References
|
// References
|
||||||
'references' => 'References',
|
'references' => 'References',
|
||||||
'references_none' => 'There are no tracked references to this item.',
|
'references_none' => 'There are no tracked references to this item.',
|
||||||
'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.',
|
'references_to_desc' => 'Listed below is all the known content in the system that links to this item.',
|
||||||
|
|
||||||
// Watch Options
|
// Watch Options
|
||||||
'watch' => 'Watch',
|
'watch' => 'Watch',
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
<a href="{{ $entity->getUrl('/references') }}" class="entity-meta-item">
|
<a href="{{ $entity->getUrl('/references') }}" class="entity-meta-item">
|
||||||
@icon('reference')
|
@icon('reference')
|
||||||
<div>
|
<div>
|
||||||
{!! trans_choice('entities.meta_reference_page_count', $referenceCount, ['count' => $referenceCount]) !!}
|
{{ trans_choice('entities.meta_reference_count', $referenceCount, ['count' => $referenceCount]) }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="book-content">
|
<div class="book-content">
|
||||||
<p class="text-muted">{!! nl2br(e($shelf->description)) !!}</p>
|
<p class="text-muted">{!! $shelf->descriptionHtml() !!}</p>
|
||||||
@if(count($sortedVisibleShelfBooks) > 0)
|
@if(count($sortedVisibleShelfBooks) > 0)
|
||||||
@if($view === 'list')
|
@if($view === 'list')
|
||||||
<div class="entity-list">
|
<div class="entity-list">
|
||||||
|
Loading…
Reference in New Issue
Block a user