Queries: Updated old use-specific entity query classes

- Updated name to align, and differentate from new 'XQueries' clases.
- Removed old sketchy base class with app resolving workarounds, to a
  proper injection-based approach.
- Also fixed wrong translation text used in PageQueries.
This commit is contained in:
Dan Brown 2024-02-08 16:39:59 +00:00
parent b77ab6f3af
commit ed21a6d798
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
10 changed files with 57 additions and 83 deletions

View file

@ -2,7 +2,7 @@
namespace BookStack\Activity\Controllers; namespace BookStack\Activity\Controllers;
use BookStack\Entities\Queries\TopFavourites; use BookStack\Entities\Queries\QueryTopFavourites;
use BookStack\Entities\Tools\MixedEntityRequestHelper; use BookStack\Entities\Tools\MixedEntityRequestHelper;
use BookStack\Http\Controller; use BookStack\Http\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -21,7 +21,7 @@ class FavouriteController extends Controller
{ {
$viewCount = 20; $viewCount = 20;
$page = intval($request->get('page', 1)); $page = intval($request->get('page', 1));
$favourites = (new TopFavourites())->run($viewCount + 1, (($page - 1) * $viewCount)); $favourites = (new QueryTopFavourites())->run($viewCount + 1, (($page - 1) * $viewCount));
$hasMoreLink = ($favourites->count() > $viewCount) ? url('/favourites?page=' . ($page + 1)) : null; $hasMoreLink = ($favourites->count() > $viewCount) ? url('/favourites?page=' . ($page + 1)) : null;

View file

@ -5,8 +5,8 @@ namespace BookStack\App;
use BookStack\Activity\ActivityQueries; use BookStack\Activity\ActivityQueries;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Queries\RecentlyViewed; use BookStack\Entities\Queries\QueryRecentlyViewed;
use BookStack\Entities\Queries\TopFavourites; use BookStack\Entities\Queries\QueryTopFavourites;
use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Tools\PageContent;
use BookStack\Http\Controller; use BookStack\Http\Controller;
use BookStack\Uploads\FaviconHandler; use BookStack\Uploads\FaviconHandler;
@ -23,8 +23,12 @@ class HomeController extends Controller
/** /**
* Display the homepage. * Display the homepage.
*/ */
public function index(Request $request, ActivityQueries $activities) public function index(
{ Request $request,
ActivityQueries $activities,
QueryRecentlyViewed $recentlyViewed,
QueryTopFavourites $topFavourites,
) {
$activity = $activities->latest(10); $activity = $activities->latest(10);
$draftPages = []; $draftPages = [];
@ -38,9 +42,9 @@ class HomeController extends Controller
$recentFactor = count($draftPages) > 0 ? 0.5 : 1; $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
$recents = $this->isSignedIn() ? $recents = $this->isSignedIn() ?
(new RecentlyViewed())->run(12 * $recentFactor, 1) $recentlyViewed->run(12 * $recentFactor, 1)
: $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); : $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
$favourites = (new TopFavourites())->run(6); $favourites = $topFavourites->run(6);
$recentlyUpdatedPages = $this->queries->pages->visibleForList() $recentlyUpdatedPages = $this->queries->pages->visibleForList()
->where('draft', false) ->where('draft', false)
->orderBy('updated_at', 'desc') ->orderBy('updated_at', 'desc')

View file

@ -1,25 +0,0 @@
<?php
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);
}
protected function entityProvider(): EntityProvider
{
return app()->make(EntityProvider::class);
}
}

View file

@ -50,7 +50,7 @@ class PageQueries implements ProvidesEntityQueries
->first(); ->first();
if (is_null($page)) { if (is_null($page)) {
throw new NotFoundException(trans('errors.chapter_not_found')); throw new NotFoundException(trans('errors.page_not_found'));
} }
return $page; return $page;

View file

@ -3,24 +3,32 @@
namespace BookStack\Entities\Queries; namespace BookStack\Entities\Queries;
use BookStack\Activity\Models\View; use BookStack\Activity\Models\View;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Permissions\PermissionApplicator;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class Popular extends EntityQuery class QueryPopular
{ {
public function __construct(
protected PermissionApplicator $permissions,
protected EntityProvider $entityProvider,
) {
}
public function run(int $count, int $page, array $filterModels = null) public function run(int $count, int $page, array $filterModels = null)
{ {
$query = $this->permissionService() $query = $this->permissions
->restrictEntityRelationQuery(View::query(), 'views', 'viewable_id', 'viewable_type') ->restrictEntityRelationQuery(View::query(), 'views', 'viewable_id', 'viewable_type')
->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count')) ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
->groupBy('viewable_id', 'viewable_type') ->groupBy('viewable_id', 'viewable_type')
->orderBy('view_count', 'desc'); ->orderBy('view_count', 'desc');
if ($filterModels) { if ($filterModels) {
$query->whereIn('viewable_type', $this->entityProvider()->getMorphClasses($filterModels)); $query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels));
} }
$entities = $query->with('viewable') $entities = $query->with('viewable')
@ -35,7 +43,7 @@ class Popular extends EntityQuery
return $entities; return $entities;
} }
protected function loadBooksForChildren(Collection $entities) protected function loadBooksForChildren(Collection $entities): void
{ {
$bookChildren = $entities->filter(fn(Entity $entity) => $entity instanceof BookChild); $bookChildren = $entities->filter(fn(Entity $entity) => $entity instanceof BookChild);
$eloquent = (new \Illuminate\Database\Eloquent\Collection($bookChildren)); $eloquent = (new \Illuminate\Database\Eloquent\Collection($bookChildren));

View file

@ -3,10 +3,18 @@
namespace BookStack\Entities\Queries; namespace BookStack\Entities\Queries;
use BookStack\Activity\Models\View; use BookStack\Activity\Models\View;
use BookStack\Entities\Tools\MixedEntityListLoader;
use BookStack\Permissions\PermissionApplicator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class RecentlyViewed extends EntityQuery class QueryRecentlyViewed
{ {
public function __construct(
protected PermissionApplicator $permissions,
protected MixedEntityListLoader $listLoader,
) {
}
public function run(int $count, int $page): Collection public function run(int $count, int $page): Collection
{ {
$user = user(); $user = user();
@ -14,7 +22,7 @@ class RecentlyViewed extends EntityQuery
return collect(); return collect();
} }
$query = $this->permissionService()->restrictEntityRelationQuery( $query = $this->permissions->restrictEntityRelationQuery(
View::query(), View::query(),
'views', 'views',
'viewable_id', 'viewable_id',
@ -28,7 +36,7 @@ class RecentlyViewed extends EntityQuery
->take($count) ->take($count)
->get(); ->get();
$this->mixedEntityListLoader()->loadIntoRelations($views->all(), 'viewable', false); $this->listLoader->loadIntoRelations($views->all(), 'viewable', false);
return $views->pluck('viewable')->filter(); return $views->pluck('viewable')->filter();
} }

View file

@ -3,10 +3,18 @@
namespace BookStack\Entities\Queries; namespace BookStack\Entities\Queries;
use BookStack\Activity\Models\Favourite; use BookStack\Activity\Models\Favourite;
use BookStack\Entities\Tools\MixedEntityListLoader;
use BookStack\Permissions\PermissionApplicator;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
class TopFavourites extends EntityQuery class QueryTopFavourites
{ {
public function __construct(
protected PermissionApplicator $permissions,
protected MixedEntityListLoader $listLoader,
) {
}
public function run(int $count, int $skip = 0) public function run(int $count, int $skip = 0)
{ {
$user = user(); $user = user();
@ -14,7 +22,7 @@ class TopFavourites extends EntityQuery
return collect(); return collect();
} }
$query = $this->permissionService() $query = $this->permissions
->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type') ->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type')
->select('favourites.*') ->select('favourites.*')
->leftJoin('views', function (JoinClause $join) { ->leftJoin('views', function (JoinClause $join) {
@ -30,7 +38,7 @@ class TopFavourites extends EntityQuery
->take($count) ->take($count)
->get(); ->get();
$this->mixedEntityListLoader()->loadIntoRelations($favourites->all(), 'favouritable', false); $this->listLoader->loadIntoRelations($favourites->all(), 'favouritable', false);
return $favourites->pluck('favouritable')->filter(); return $favourites->pluck('favouritable')->filter();
} }

View file

@ -3,20 +3,13 @@
namespace BookStack\Entities\Tools; namespace BookStack\Entities\Tools;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\EntityProvider; use BookStack\Entities\Queries\EntityQueries;
use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\Relations\Relation;
class MixedEntityListLoader 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( public function __construct(
protected EntityProvider $entityProvider protected EntityQueries $queries,
) { ) {
} }
@ -61,14 +54,7 @@ class MixedEntityListLoader
$modelMap = []; $modelMap = [];
foreach ($idsByType as $type => $ids) { foreach ($idsByType as $type => $ids) {
if (!isset($this->listAttributes[$type])) { $models = $this->queries->visibleForList($type)
continue;
}
$instance = $this->entityProvider->get($type);
$models = $instance->newQuery()
->select(array_merge($this->listAttributes[$type], $this->getSubSelectsForQuery($type)))
->scopes('visible')
->whereIn('id', $ids) ->whereIn('id', $ids)
->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : []) ->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : [])
->get(); ->get();
@ -100,19 +86,4 @@ class MixedEntityListLoader
return $toLoad; 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;
}
} }

View file

@ -3,7 +3,7 @@
namespace BookStack\Search; namespace BookStack\Search;
use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Queries\Popular; use BookStack\Entities\Queries\QueryPopular;
use BookStack\Entities\Tools\SiblingFetcher; use BookStack\Entities\Tools\SiblingFetcher;
use BookStack\Http\Controller; use BookStack\Http\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -67,7 +67,7 @@ class SearchController extends Controller
* Search for a list of entities and return a partial HTML response of matching entities. * Search for a list of entities and return a partial HTML response of matching entities.
* Returns the most popular entities if no search is provided. * Returns the most popular entities if no search is provided.
*/ */
public function searchForSelector(Request $request) public function searchForSelector(Request $request, QueryPopular $queryPopular)
{ {
$entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book']; $entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book'];
$searchTerm = $request->get('term', false); $searchTerm = $request->get('term', false);
@ -78,7 +78,7 @@ class SearchController extends Controller
$searchTerm .= ' {type:' . implode('|', $entityTypes) . '}'; $searchTerm .= ' {type:' . implode('|', $entityTypes) . '}';
$entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results']; $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results'];
} else { } else {
$entities = (new Popular())->run(20, 0, $entityTypes); $entities = $queryPopular->run(20, 0, $entityTypes);
} }
return view('search.parts.entity-selector-list', ['entities' => $entities, 'permission' => $permission]); return view('search.parts.entity-selector-list', ['entities' => $entities, 'permission' => $permission]);

View file

@ -1,5 +1,5 @@
@extends('layouts.simple') @extends('layouts.simple')
@inject('popular', \BookStack\Entities\Queries\QueryPopular::class)
@section('content') @section('content')
<div class="container mt-l"> <div class="container mt-l">
@ -28,7 +28,7 @@
<div class="card mb-xl"> <div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.pages_popular') }}</h3> <h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
<div class="px-m"> <div class="px-m">
@include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact']) @include('entities.list', ['entities' => $popular->run(10, 0, ['page']), 'style' => 'compact'])
</div> </div>
</div> </div>
</div> </div>
@ -36,7 +36,7 @@
<div class="card mb-xl"> <div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.books_popular') }}</h3> <h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
<div class="px-m"> <div class="px-m">
@include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact']) @include('entities.list', ['entities' => $popular->run(10, 0, ['book']), 'style' => 'compact'])
</div> </div>
</div> </div>
</div> </div>
@ -44,7 +44,7 @@
<div class="card mb-xl"> <div class="card mb-xl">
<h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3> <h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
<div class="px-m"> <div class="px-m">
@include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact']) @include('entities.list', ['entities' => $popular->run(10, 0, ['chapter']), 'style' => 'compact'])
</div> </div>
</div> </div>
</div> </div>