use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
+use BookStack\Entities\Tools\MixedEntityListLoader;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder;
class ActivityQueries
{
- protected PermissionApplicator $permissions;
-
- public function __construct(PermissionApplicator $permissions)
- {
- $this->permissions = $permissions;
+ public function __construct(
+ protected PermissionApplicator $permissions,
+ protected MixedEntityListLoader $listLoader,
+ ) {
}
/**
$activityList = $this->permissions
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
- ->with(['user', 'entity'])
+ ->with(['user'])
->skip($count * $page)
->take($count)
->get();
+ $this->listLoader->loadIntoRelations($activityList->all(), 'entity', false);
+
return $this->filterSimilar($activityList);
}
use BookStack\Activity\ActivityQueries;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
+use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Queries\RecentlyViewed;
use BookStack\Entities\Queries\TopFavourites;
use BookStack\Entities\Repos\BookRepo;
$draftPages = [];
if ($this->isSignedIn()) {
- $draftPages = Page::visible()
- ->where('draft', '=', true)
- ->where('created_by', '=', user()->id)
+ $draftPages = PageQueries::currentUserDraftsForList()
->orderBy('updated_at', 'desc')
->with('book')
->take(6)
(new RecentlyViewed())->run(12 * $recentFactor, 1)
: Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
$favourites = (new TopFavourites())->run(6);
- $recentlyUpdatedPages = Page::visible()->with('book')
+ $recentlyUpdatedPages = PageQueries::visibleForList()
->where('draft', false)
->orderBy('updated_at', 'desc')
->take($favourites->count() > 0 ? 5 : 10)
- ->select(Page::$listAttributes)
->get();
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
$homepageSetting = setting('app-homepage', '0:');
$id = intval(explode(':', $homepageSetting)[0]);
/** @var Page $customHomepage */
- $customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id);
+ $customHomepage = PageQueries::start()->where('draft', '=', false)->findOrFail($id);
$pageContent = new PageContent($customHomepage);
$customHomepage->html = $pageContent->render(false);
*/
abstract class BookChild extends Entity
{
- protected static function boot()
- {
- parent::boot();
-
- // Load book slugs onto these models by default during query-time
- static::addGlobalScope('book_slug', function (Builder $builder) {
- $builder->addSelect(['book_slug' => function ($builder) {
- $builder->select('slug')
- ->from('books')
- ->whereColumn('books.id', '=', 'book_id');
- }]);
- });
- }
-
/**
* Scope a query to find items where the child has the given childSlug
* where its parent has the bookSlug.
namespace BookStack\Entities\Queries;
use BookStack\Entities\EntityProvider;
+use BookStack\Entities\Tools\MixedEntityListLoader;
use BookStack\Permissions\PermissionApplicator;
abstract class EntityQuery
{
+ protected function mixedEntityListLoader(): MixedEntityListLoader
+ {
+ return app()->make(MixedEntityListLoader::class);
+ }
+
protected function permissionService(): PermissionApplicator
{
return app()->make(PermissionApplicator::class);
--- /dev/null
+<?php
+
+namespace BookStack\Entities\Queries;
+
+use BookStack\Entities\Models\Page;
+use Illuminate\Database\Eloquent\Builder;
+
+class PageQueries
+{
+ public static function start(): Builder
+ {
+ return Page::query();
+ }
+
+ public static function visibleForList(): Builder
+ {
+ return Page::visible()
+ ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) {
+ $builder->select('slug')
+ ->from('books')
+ ->whereColumn('books.id', '=', 'pages.book_id');
+ }]));
+ }
+
+ public static function currentUserDraftsForList(): Builder
+ {
+ return static::visibleForList()
+ ->where('draft', '=', true)
+ ->where('created_by', '=', user()->id);
+ }
+}
public function run(int $count, int $page): Collection
{
$user = user();
- if ($user === null || $user->isGuest()) {
+ if ($user->isGuest()) {
return collect();
}
->orderBy('views.updated_at', 'desc')
->where('user_id', '=', user()->id);
- return $query->with('viewable')
+ $views = $query
->skip(($page - 1) * $count)
->take($count)
- ->get()
- ->pluck('viewable')
- ->filter();
+ ->get();
+
+ $this->mixedEntityListLoader()->loadIntoRelations($views->all(), 'viewable', false);
+
+ return $views->pluck('viewable')->filter();
}
}
->orderBy('views.views', 'desc')
->where('favourites.user_id', '=', user()->id);
- return $query->with('favouritable')
+ $favourites = $query
->skip($skip)
->take($count)
- ->get()
- ->pluck('favouritable')
- ->filter();
+ ->get();
+
+ $this->mixedEntityListLoader()->loadIntoRelations($favourites->all(), 'favouritable', false);
+
+ return $favourites->pluck('favouritable')->filter();
}
}
* 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
+ public function loadIntoRelations(array $relations, string $relationName, bool $loadParents): void
{
$idsByType = [];
foreach ($relations as $relation) {
$idsByType[$type][] = $id;
}
- $modelMap = $this->idsByTypeToModelMap($idsByType);
+ $modelMap = $this->idsByTypeToModelMap($idsByType, $loadParents);
foreach ($relations as $relation) {
$type = $relation->getAttribute($relationName . '_type');
* @param array<string, int[]> $idsByType
* @return array<string, array<int, Model>>
*/
- protected function idsByTypeToModelMap(array $idsByType): array
+ protected function idsByTypeToModelMap(array $idsByType, bool $eagerLoadParents): array
{
$modelMap = [];
$instance = $this->entityProvider->get($type);
$models = $instance->newQuery()
- ->select($this->listAttributes[$type])
+ ->select(array_merge($this->listAttributes[$type], $this->getSubSelectsForQuery($type)))
->scopes('visible')
->whereIn('id', $ids)
- ->with($this->getRelationsToEagerLoad($type))
+ ->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : [])
->get();
if (count($models) > 0) {
return $toLoad;
}
+
+ protected function getSubSelectsForQuery(string $type): array
+ {
+ $subSelects = [];
+
+ if ($type === 'chapter' || $type === 'page') {
+ $subSelects['book_slug'] = function ($builder) {
+ $builder->select('slug')
+ ->from('books')
+ ->whereColumn('books.id', '=', 'book_id');
+ };
+ }
+
+ return $subSelects;
+ }
}
public function getReferencesToEntity(Entity $entity): Collection
{
$references = $this->queryReferencesToEntity($entity)->get();
- $this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from');
+ $this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from', true);
return $references;
}
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('views', function (Blueprint $table) {
+ $table->index(['updated_at'], 'views_updated_at_index');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('views', function (Blueprint $table) {
+ $table->dropIndex('views_updated_at_index');
+ });
+ }
+};