]> BookStack Code Mirror - bookstack/commitdiff
Revamped some complex queries, added favourites to home
authorDan Brown <redacted>
Sat, 22 May 2021 13:05:28 +0000 (14:05 +0100)
committerDan Brown <redacted>
Sat, 22 May 2021 13:05:28 +0000 (14:05 +0100)
- Removed old view system and started use of new query classes instead.
- Finished off RelationMultiModelQuery but found it was less efficient
than x-many queries due to the amount of tables being scanned.
Adding now for history but will delete as not used.
- Updated recently viewed to use same query system as popular items
  rather than running and joining x-entities queries.
- Added "Most Viewed Faviourites" listing to homepages.

15 files changed:
app/Actions/View.php
app/Actions/ViewService.php [deleted file]
app/Config/app.php
app/Entities/Queries/EntityQuery.php [new file with mode: 0644]
app/Entities/Queries/Popular.php [new file with mode: 0644]
app/Entities/Queries/RecentlyViewed.php [new file with mode: 0644]
app/Entities/Queries/TopFavourites.php [new file with mode: 0644]
app/Entities/Tools/RelationMultiModelQuery.php
app/Facades/Views.php [deleted file]
app/Http/Controllers/HomeController.php
app/Http/Controllers/SearchController.php
resources/lang/en/entities.php
resources/views/common/home-sidebar.blade.php
resources/views/common/home.blade.php
resources/views/errors/404.blade.php

index 62e03d9f444f6b87946cb1d1b1f11a139108fa29..de30900c76f2a6d4600a75f726dd9645fe7713ff 100644 (file)
@@ -42,7 +42,7 @@ class View extends Model
             'user_id' => $user->id,
         ], ['views' => 0]);
 
-        $view->save(['views' => $view->views + 1]);
+        $view->forceFill(['views' => $view->views + 1])->save();
 
         return $view->views;
     }
diff --git a/app/Actions/ViewService.php b/app/Actions/ViewService.php
deleted file mode 100644 (file)
index febc93a..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php namespace BookStack\Actions;
-
-use BookStack\Auth\Permissions\PermissionService;
-use BookStack\Entities\Models\Entity;
-use BookStack\Entities\EntityProvider;
-use DB;
-use Illuminate\Support\Collection;
-
-class ViewService
-{
-    protected $view;
-    protected $permissionService;
-    protected $entityProvider;
-
-    /**
-     * ViewService constructor.
-     * @param View $view
-     * @param PermissionService $permissionService
-     * @param EntityProvider $entityProvider
-     */
-    public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider)
-    {
-        $this->view = $view;
-        $this->permissionService = $permissionService;
-        $this->entityProvider = $entityProvider;
-    }
-
-    /**
-     * Get the entities with the most views.
-     * @param int $count
-     * @param int $page
-     * @param string|array $filterModels
-     * @param string $action - used for permission checking
-     * @return Collection
-     */
-    public function getPopular(int $count = 10, int $page = 0, array $filterModels = null, string $action = 'view')
-    {
-        $skipCount = $count * $page;
-        $query = $this->permissionService
-            ->filterRestrictedEntityRelations($this->view->newQuery(), 'views', 'viewable_id', 'viewable_type', $action)
-            ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
-            ->groupBy('viewable_id', 'viewable_type')
-            ->orderBy('view_count', 'desc');
-
-        if ($filterModels) {
-            $query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels));
-        }
-
-        return $query->with('viewable')
-            ->skip($skipCount)
-            ->take($count)
-            ->get()
-            ->pluck('viewable')
-            ->filter();
-    }
-
-    /**
-     * Get all recently viewed entities for the current user.
-     */
-    public function getUserRecentlyViewed(int $count = 10, int $page = 1)
-    {
-        $user = user();
-        if ($user === null || $user->isDefault()) {
-            return collect();
-        }
-
-        $all = collect();
-        /** @var Entity $instance */
-        foreach ($this->entityProvider->all() as $name => $instance) {
-            $items = $instance::visible()->withLastView()
-                ->having('last_viewed_at', '>', 0)
-                ->orderBy('last_viewed_at', 'desc')
-                ->skip($count * ($page - 1))
-                ->take($count)
-                ->get();
-            $all = $all->concat($items);
-        }
-
-        return $all->sortByDesc('last_viewed_at')->slice(0, $count);
-    }
-}
index 065845f961258f0fc04d0c9c46d18ddacc50f7f4..88f38423aa422f74c027274d06b093b9b09f18d9 100755 (executable)
@@ -184,11 +184,9 @@ return [
 
         // Custom BookStack
         'Activity' => BookStack\Facades\Activity::class,
-        'Views'    => BookStack\Facades\Views::class,
         'Images'   => BookStack\Facades\Images::class,
         'Permissions' => BookStack\Facades\Permissions::class,
         'Theme'    => BookStack\Facades\Theme::class,
-
     ],
 
     // Proxy configuration
diff --git a/app/Entities/Queries/EntityQuery.php b/app/Entities/Queries/EntityQuery.php
new file mode 100644 (file)
index 0000000..bd920c3
--- /dev/null
@@ -0,0 +1,17 @@
+<?php namespace BookStack\Entities\Queries;
+
+use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Entities\EntityProvider;
+
+abstract class EntityQuery
+{
+    protected function permissionService(): PermissionService
+    {
+        return app()->make(PermissionService::class);
+    }
+
+    protected function entityProvider(): EntityProvider
+    {
+        return app()->make(EntityProvider::class);
+    }
+}
\ No newline at end of file
diff --git a/app/Entities/Queries/Popular.php b/app/Entities/Queries/Popular.php
new file mode 100644 (file)
index 0000000..98db2fe
--- /dev/null
@@ -0,0 +1,29 @@
+<?php namespace BookStack\Entities\Queries;
+
+
+use BookStack\Actions\View;
+use Illuminate\Support\Facades\DB;
+
+class Popular extends EntityQuery
+{
+    public function run(int $count, int $page, array $filterModels = null, string $action = 'view')
+    {
+        $query = $this->permissionService()
+            ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', $action)
+            ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
+            ->groupBy('viewable_id', 'viewable_type')
+            ->orderBy('view_count', 'desc');
+
+        if ($filterModels) {
+            $query->whereIn('viewable_type', $this->entityProvider()->getMorphClasses($filterModels));
+        }
+
+        return $query->with('viewable')
+            ->skip($count * ($page - 1))
+            ->take($count)
+            ->get()
+            ->pluck('viewable')
+            ->filter();
+    }
+
+}
\ No newline at end of file
diff --git a/app/Entities/Queries/RecentlyViewed.php b/app/Entities/Queries/RecentlyViewed.php
new file mode 100644 (file)
index 0000000..d528fea
--- /dev/null
@@ -0,0 +1,32 @@
+<?php namespace BookStack\Entities\Queries;
+
+use BookStack\Actions\View;
+use Illuminate\Support\Collection;
+
+class RecentlyViewed extends EntityQuery
+{
+    public function run(int $count, int $page): Collection
+    {
+        $user = user();
+        if ($user === null || $user->isDefault()) {
+            return collect();
+        }
+
+        $query = $this->permissionService()->filterRestrictedEntityRelations(
+            View::query(),
+            'views',
+            'viewable_id',
+            'viewable_type',
+            'view'
+        )
+            ->orderBy('views.updated_at', 'desc')
+            ->where('user_id', '=', user()->id);
+
+        return $query->with('viewable')
+            ->skip(($page - 1) * $count)
+            ->take($count)
+            ->get()
+            ->pluck('viewable')
+            ->filter();
+    }
+}
diff --git a/app/Entities/Queries/TopFavourites.php b/app/Entities/Queries/TopFavourites.php
new file mode 100644 (file)
index 0000000..aaacdc5
--- /dev/null
@@ -0,0 +1,36 @@
+<?php namespace BookStack\Entities\Queries;
+
+
+use BookStack\Actions\View;
+use Illuminate\Database\Query\JoinClause;
+use Illuminate\Support\Facades\DB;
+
+class TopFavourites extends EntityQuery
+{
+    public function run(int $count, int $page)
+    {
+        $user = user();
+        if ($user === null || $user->isDefault()) {
+            return collect();
+        }
+
+        $query = $this->permissionService()
+            ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', 'view')
+            ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
+            ->groupBy('viewable_id', 'viewable_type')
+            ->rightJoin('favourites', function (JoinClause $join) {
+                $join->on('views.viewable_id', '=', 'favourites.favouritable_id');
+                $join->on('views.viewable_type', '=', 'favourites.favouritable_type');
+                $join->where('favourites.user_id', '=', user()->id);
+            })
+            ->orderBy('view_count', 'desc');
+
+        return $query->with('viewable')
+            ->skip($count * ($page - 1))
+            ->take($count)
+            ->get()
+            ->pluck('viewable')
+            ->filter();
+    }
+
+}
\ No newline at end of file
index 68a90acd0bd748a2d151fe42c2e1d7107f049d06..c992fe454e51504c01344a89e5b123ba8c086620 100644 (file)
@@ -18,28 +18,6 @@ use Illuminate\Support\Collection;
  */
 class RelationMultiModelQuery
 {
-
-    // TODO - Hydrate results to models
-    // TODO - Allow setting additional wheres and all-model columns (From the core relation - eg, last_viewed_at)
-
-//select views.updated_at as last_viewed_at,
-//b.id as book_id, b.name as book_name, b.slug as book_slug, b.description as book_description,
-//s.id as bookshelf_id, s.name as bookshelf_name, s.slug as bookshelf_slug, s.description as bookshelf_description,
-//c.id as chapter_id, c.name as chapter_name, c.slug as chapter_slug, c.description as chapter_description,
-//p.id as page_id, p.name as page_name, p.slug as page_slug, p.text as page_description
-//from views
-//left join bookshelves s on (s.id = views.viewable_id and views.viewable_type = 'BookStack\\Bookshelf' and s.deleted_at is null)
-//left join books b on (b.id = views.viewable_id and views.viewable_type = 'BookStack\\Book' and b.deleted_at is null)
-//left join chapters c on (c.id = views.viewable_id and views.viewable_type = 'BookStack\\Chapter' and c.deleted_at is null)
-//left join pages p on (p.id = views.viewable_id and views.viewable_type = 'BookStack\\Page' and p.deleted_at is null)
-//#     Permissions
-//where exists(
-//select * from joint_permissions jp where jp.entity_id = views.viewable_id and jp.entity_type = views.viewable_type
-//and jp.action = 'view' and jp.role_id in (1, 2, 3, 6, 12) and (jp.has_permission = 1 or (jp.has_permission_own = 1 and jp.owned_by = 1))
-//)
-//and (s.id is not null or b.id is not null or c.id is not null or p.id is not null)
-//and views.user_id = 1
-
     /** @var array<string, array> */
     protected $lookupModels = [];
 
@@ -49,9 +27,52 @@ class RelationMultiModelQuery
     /** @var string */
     protected $polymorphicFieldName;
 
-    public function __construct(Model $relation, string $polymorphicFieldName)
+    /**
+     * The keys are relation fields to fetch.
+     * The values are the name to use for the resulting model attribute.
+     * @var array<string, string>
+     */
+    protected $relationFields = [];
+
+    /**
+     * An array of [string $col, string $operator, mixed $value] where conditions.
+     * @var array<array>>
+     */
+    protected $relationWheres = [];
+
+    /**
+     * Field on the relation field to order by.
+     * @var ?array[string $column, string $direction]
+     */
+    protected $orderByRelationField = null;
+
+    /**
+     * Number of results to take
+     * @var ?int
+     */
+    protected $take = null;
+
+    /**
+     * Number of results to skip.
+     * @var ?int
+     */
+    protected $skip = null;
+
+    /**
+     * Callback that will receive the query for any advanced customization.
+     * @var ?callable
+     */
+    protected $queryCustomizer = null;
+
+    /**
+     * @throws \Exception
+     */
+    public function __construct(string $relation, string $polymorphicFieldName)
     {
-        $this->relation = $relation;
+        $this->relation = (new $relation);
+        if (!$this->relation instanceof Model) {
+            throw new \Exception('Given relation must be a model instance class');
+        }
         $this->polymorphicFieldName = $polymorphicFieldName;
     }
 
@@ -76,6 +97,78 @@ class RelationMultiModelQuery
         return $this;
     }
 
+    /**
+     * Bring back a field from the relation object with the model results.
+     */
+    public function withRelationField(string $fieldName, string $modelAttributeName): self
+    {
+        $this->relationFields[$fieldName] = $modelAttributeName;
+        return $this;
+    }
+
+    /**
+     * Add a where condition to the query for the main relation table.
+     */
+    public function whereRelation(string $column, string $operator, $value): self
+    {
+        $this->relationWheres[] = [$column, $operator, $value];
+        return $this;
+    }
+
+    /**
+     * Order by the given relation column.
+     */
+    public function orderByRelation(string $column, string $direction = 'asc'): self
+    {
+        $this->orderByRelationField = [$column, $direction];
+        return $this;
+    }
+
+    /**
+     * Skip the given $count of results in the query.
+     */
+    public function skip(?int $count): self
+    {
+        $this->skip = $count;
+        return $this;
+    }
+
+    /**
+     * Take the given $count of results in the query.
+     */
+    public function take(?int $count): self
+    {
+        $this->take = $count;
+        return $this;
+    }
+
+    /**
+     * Pass a callable, which will receive the base query
+     * to perform additional custom operations on the query.
+     */
+    public function customizeUsing(callable $customizer): self
+    {
+        $this->queryCustomizer = $customizer;
+        return $this;
+    }
+
+    /**
+     * Get the SQL from the core query being ran.
+     */
+    public function toSql(): string
+    {
+        return $this->build()->toSql();
+    }
+
+    /**
+     * Run the query and get the results.
+     */
+    public function run(): Collection
+    {
+        $results = $this->build()->get();
+        return $this->hydrateModelsFromResults($results);
+    }
+
     /**
      * Build the core query to run.
      */
@@ -85,6 +178,14 @@ class RelationMultiModelQuery
         $relationTable = $this->relation->getTable();
         $modelTables = [];
 
+        // Load relation fields
+        foreach ($this->relationFields as $relationField => $alias) {
+            $query->addSelect(
+                $relationTable . '.' . $relationField . ' as '
+                . $relationTable . '@' . $relationField
+            );
+        }
+
         // Load model selects & joins
         foreach ($this->lookupModels as $lookupModel => $columns) {
             /** @var Entity $model */
@@ -107,11 +208,34 @@ class RelationMultiModelQuery
             }
         });
 
+        // Add relation wheres
+        foreach ($this->relationWheres as [$column, $operator, $value]) {
+            $query->where($relationTable . '.' . $column, $operator, $value);
+        }
+
+        // Skip and take
+        if (!is_null($this->skip)) {
+            $query->skip($this->skip);
+        }
+        if (!is_null($this->take)) {
+            $query->take($this->take);
+        }
+        if (!is_null($this->queryCustomizer)) {
+            $customizer = $this->queryCustomizer;
+            $customizer($query);
+        }
+        if (!is_null($this->orderByRelationField)) {
+            $query->orderBy($relationTable . '.' . $this->orderByRelationField[0], $this->orderByRelationField[1]);
+        }
+
         $this->applyPermissionsToQuery($query, 'view');
 
         return $query;
     }
 
+    /**
+     * Run the query through the permission system.
+     */
     protected function applyPermissionsToQuery(Builder $query, string $action)
     {
         $permissions = app()->make(PermissionService::class);
@@ -131,24 +255,54 @@ class RelationMultiModelQuery
     {
         $selectArray = [];
         foreach ($columns as $column) {
-            $selectArray[] = $table . '.' . $column . ' as '.  $table . '_' . $column;
+            $selectArray[] = $table . '.' . $column . ' as ' . $table . '@' . $column;
         }
         return $selectArray;
     }
 
     /**
-     * Get the SQL from the core query being ran.
+     * Hydrate a collection of result data into models.
      */
-    public function toSql(): string
+    protected function hydrateModelsFromResults(Collection $results): Collection
     {
-        return $this->build()->toSql();
+        $modelByIdColumn = [];
+        foreach ($this->lookupModels as $lookupModel => $columns) {
+            /** @var Model $model */
+            $model = new $lookupModel;
+            $modelByIdColumn[$model->getTable() . '@id'] = $model;
+        }
+
+        return $results->map(function ($result) use ($modelByIdColumn) {
+            foreach ($modelByIdColumn as $idColumn => $modelInstance) {
+                if (isset($result->$idColumn)) {
+                    return $this->hydrateModelFromResult($modelInstance, $result);
+                }
+            }
+            return null;
+        });
     }
 
     /**
-     * Run the query and get the results.
+     * Hydrate the given model type with the database result.
      */
-    public function run(): Collection
+    protected function hydrateModelFromResult(Model $model, \stdClass $result): Model
     {
-        return $this->build()->get();
+        $modelPrefix = $model->getTable() . '@';
+        $relationPrefix = $this->relation->getTable() . '@';
+        $attrs = [];
+
+        foreach ((array) $result as $col => $value) {
+            if (strpos($col, $modelPrefix) === 0) {
+                $attrName = substr($col, strlen($modelPrefix));
+                $attrs[$attrName] = $value;
+            }
+            if (strpos($col, $relationPrefix) === 0) {
+                $col = substr($col, strlen($relationPrefix));
+                $attrName = $this->relationFields[$col];
+                $attrs[$attrName] = $value;
+            }
+        }
+
+        return $model->newInstance()->forceFill($attrs);
     }
-}
\ No newline at end of file
+}
diff --git a/app/Facades/Views.php b/app/Facades/Views.php
deleted file mode 100644 (file)
index f535711..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php namespace BookStack\Facades;
-
-use Illuminate\Support\Facades\Facade;
-
-class Views extends Facade
-{
-    /**
-     * Get the registered name of the component.
-     *
-     * @return string
-     */
-    protected static function getFacadeAccessor()
-    {
-        return 'views';
-    }
-}
index 1ffb99f8d427a00672683af60c35f6061e015705..f5ab212040c6eb5dc1f5f411999b32ec5f56c492 100644 (file)
@@ -2,11 +2,12 @@
 
 use Activity;
 use BookStack\Entities\Models\Book;
+use BookStack\Entities\Queries\RecentlyViewed;
+use BookStack\Entities\Queries\TopFavourites;
 use BookStack\Entities\Tools\PageContent;
 use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\BookRepo;
 use BookStack\Entities\Repos\BookshelfRepo;
-use Illuminate\Http\Response;
 use Views;
 
 class HomeController extends Controller
@@ -32,12 +33,13 @@ class HomeController extends Controller
 
         $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
         $recents = $this->isSignedIn() ?
-              Views::getUserRecentlyViewed(12*$recentFactor, 1)
+            (new RecentlyViewed)->run(12*$recentFactor, 1)
             : Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
+        $faviourites = (new TopFavourites)->run(6, 1);
         $recentlyUpdatedPages = Page::visible()->with('book')
             ->where('draft', false)
             ->orderBy('updated_at', 'desc')
-            ->take(12)
+            ->take($faviourites->count() > 0 ? 6 : 12)
             ->get();
 
         $homepageOptions = ['default', 'books', 'bookshelves', 'page'];
@@ -51,6 +53,7 @@ class HomeController extends Controller
             'recents' => $recents,
             'recentlyUpdatedPages' => $recentlyUpdatedPages,
             'draftPages' => $draftPages,
+            'favourites' => $faviourites,
         ];
 
         // Add required list ordering & sorting for books & shelves views.
index bb824fd9bfedf1dec20799df7ab25d0b536774e3..1f77e6377ed3bf13c8fb5aecce56036157e9a5fb 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Http\Controllers;
 
 use BookStack\Actions\ViewService;
+use BookStack\Entities\Queries\Popular;
 use BookStack\Entities\Tools\SearchRunner;
 use BookStack\Entities\Tools\ShelfContext;
 use BookStack\Entities\Tools\SearchOptions;
@@ -82,7 +83,7 @@ class SearchController extends Controller
             $searchTerm .= ' {type:'. implode('|', $entityTypes) .'}';
             $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20, $permission)['results'];
         } else {
-            $entities = $this->viewService->getPopular(20, 0, $entityTypes, $permission);
+            $entities = (new Popular)->run(20, 0, $entityTypes, $permission);
         }
 
         return view('search.entity-ajax-list', ['entities' => $entities]);
index 1661bae57cae5184af9381b15f79fe0c301d003a..6c3341368ee19ba7988909bac39507e1a23722fe 100644 (file)
@@ -27,6 +27,7 @@ return [
     'images' => 'Images',
     'my_recent_drafts' => 'My Recent Drafts',
     'my_recently_viewed' => 'My Recently Viewed',
+    'my_most_viewed_favourites' => 'My Most Viewed Favourites',
     'no_pages_viewed' => 'You have not viewed any pages',
     'no_pages_recently_created' => 'No pages have been recently created',
     'no_pages_recently_updated' => 'No pages have been recently updated',
index 4c36ce61a9be9648f48dbc68462b2b75bc73f83a..b66c8495d4f159742ccaedfe3674b231d714fffe 100644 (file)
@@ -5,6 +5,18 @@
     </div>
 @endif
 
+@if(count($favourites) > 0)
+    <div id="top-favourites" class="card mb-xl">
+        <h3 class="card-title">{{ trans('entities.my_most_viewed_favourites') }}</h3>
+        <div class="px-m">
+            @include('partials.entity-list', [
+            'entities' => $favourites,
+            'style' => 'compact',
+            ])
+        </div>
+    </div>
+@endif
+
 <div class="mb-xl">
     <h5>{{ trans('entities.' . (auth()->check() ? 'my_recently_viewed' : 'books_recent')) }}</h5>
     @include('partials.entity-list', [
index ad503463e46f1db404882fbf58018dfae39b227c..acfca97b2ef205474202724a3a8ec2e73eb40550 100644 (file)
             </div>
 
             <div>
+                @if(count($favourites) > 0)
+                    <div id="top-favourites" class="card mb-xl">
+                        <h3 class="card-title">{{ trans('entities.my_most_viewed_favourites') }}</h3>
+                        <div class="px-m">
+                            @include('partials.entity-list', [
+                            'entities' => $favourites,
+                            'style' => 'compact',
+                            ])
+                        </div>
+                    </div>
+                @endif
+
                 <div id="recent-pages" class="card mb-xl">
                     <h3 class="card-title"><a class="no-color" href="{{ url("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
                     <div id="recently-updated-pages" class="px-m">
index b3325ba828805da62ae78a7d3ca0b0fe8cd81bff..d4d8ed76ba50bd47b83832ea8f04e17ebadca98a 100644 (file)
@@ -26,7 +26,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['page']), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
@@ -34,7 +34,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['book']), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
@@ -42,7 +42,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['chapter']), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>