3 namespace BookStack\Entities\Tools;
5 use BookStack\App\Model;
6 use BookStack\Entities\EntityProvider;
7 use Illuminate\Database\Eloquent\Relations\Relation;
9 class MixedEntityListLoader
11 protected array $listAttributes = [
12 'page' => ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text', 'draft'],
13 'chapter' => ['id', 'name', 'slug', 'book_id', 'description'],
14 'book' => ['id', 'name', 'slug', 'description'],
15 'bookshelf' => ['id', 'name', 'slug', 'description'],
18 public function __construct(
19 protected EntityProvider $entityProvider
24 * Efficiently load in entities for listing onto the given list
25 * where entities are set as a relation via the given name.
26 * This will look for a model id and type via 'name_id' and 'name_type'.
27 * @param Model[] $relations
29 public function loadIntoRelations(array $relations, string $relationName): void
32 foreach ($relations as $relation) {
33 $type = $relation->getAttribute($relationName . '_type');
34 $id = $relation->getAttribute($relationName . '_id');
36 if (!isset($idsByType[$type])) {
37 $idsByType[$type] = [];
40 $idsByType[$type][] = $id;
43 $modelMap = $this->idsByTypeToModelMap($idsByType);
45 foreach ($relations as $relation) {
46 $type = $relation->getAttribute($relationName . '_type');
47 $id = $relation->getAttribute($relationName . '_id');
48 $related = $modelMap[$type][strval($id)] ?? null;
50 $relation->setRelation($relationName, $related);
56 * @param array<string, int[]> $idsByType
57 * @return array<string, array<int, Model>>
59 protected function idsByTypeToModelMap(array $idsByType): array
63 foreach ($idsByType as $type => $ids) {
64 if (!isset($this->listAttributes[$type])) {
68 $instance = $this->entityProvider->get($type);
69 $models = $instance->newQuery()
70 ->select($this->listAttributes[$type])
73 ->with($this->getRelationsToEagerLoad($type))
76 if (count($models) > 0) {
77 $modelMap[$type] = [];
80 foreach ($models as $model) {
81 $modelMap[$type][strval($model->id)] = $model;
88 protected function getRelationsToEagerLoad(string $type): array
91 $loadVisible = fn (Relation $query) => $query->scopes('visible');
93 if ($type === 'chapter' || $type === 'page') {
94 $toLoad['book'] = $loadVisible;
97 if ($type === 'page') {
98 $toLoad['chapter'] = $loadVisible;