From 5611c8c0da8927cd4831a9dd23a022a2d9bcfa18 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 30 May 2026 21:44:25 +0100 Subject: [PATCH 01/37] Maintenance: Started work on PHPStan Level 5 --- app/Access/ExternalBaseUserProvider.php | 2 +- app/Access/Guards/ExternalBaseSessionGuard.php | 8 ++++++-- app/Access/Guards/LdapSessionGuard.php | 4 ++++ app/Access/Oidc/OidcJwtWithClaims.php | 2 -- app/Activity/Models/Comment.php | 8 +++++++- app/Api/ApiDocsGenerator.php | 4 ++-- app/App/Providers/ThemeServiceProvider.php | 7 ++++++- app/App/helpers.php | 3 ++- app/Entities/Models/Entity.php | 2 +- app/Entities/Queries/QueryPopular.php | 2 ++ app/Entities/Queries/QueryRecentlyViewed.php | 2 ++ app/Entities/Queries/QueryTopFavourites.php | 2 ++ app/Http/Controller.php | 3 ++- app/Util/DatabaseTransaction.php | 3 ++- phpstan.neon.dist | 2 +- 15 files changed, 40 insertions(+), 14 deletions(-) diff --git a/app/Access/ExternalBaseUserProvider.php b/app/Access/ExternalBaseUserProvider.php index 2165fd4591e..2bc15cb0305 100644 --- a/app/Access/ExternalBaseUserProvider.php +++ b/app/Access/ExternalBaseUserProvider.php @@ -42,7 +42,7 @@ public function updateRememberToken(Authenticatable $user, $token) /** * Retrieve a user by the given credentials. */ - public function retrieveByCredentials(array $credentials): ?Authenticatable + public function retrieveByCredentials(array $credentials): ?User { return User::query() ->where('external_auth_id', $credentials['external_auth_id']) diff --git a/app/Access/Guards/ExternalBaseSessionGuard.php b/app/Access/Guards/ExternalBaseSessionGuard.php index 91239599ba9..b7bf174a1de 100644 --- a/app/Access/Guards/ExternalBaseSessionGuard.php +++ b/app/Access/Guards/ExternalBaseSessionGuard.php @@ -50,8 +50,12 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Create a new authentication guard. */ - public function __construct(string $name, UserProvider $provider, Session $session, RegistrationService $registrationService) - { + public function __construct( + string $name, + UserProvider $provider, + Session $session, + RegistrationService $registrationService + ) { $this->name = $name; $this->session = $session; $this->provider = $provider; diff --git a/app/Access/Guards/LdapSessionGuard.php b/app/Access/Guards/LdapSessionGuard.php index 9455d530dfe..f3628c897d8 100644 --- a/app/Access/Guards/LdapSessionGuard.php +++ b/app/Access/Guards/LdapSessionGuard.php @@ -83,6 +83,10 @@ public function attempt(array $credentials = [], $remember = false): bool } } + if (!($user instanceof User)) { + throw new LoginAttemptException('Could not find or create a user for LDAP login.'); + } + // Sync LDAP groups if required if ($this->ldapService->shouldSyncGroups()) { $this->ldapService->syncGroups($user, $username); diff --git a/app/Access/Oidc/OidcJwtWithClaims.php b/app/Access/Oidc/OidcJwtWithClaims.php index 9d7eeead1a9..9763ab15864 100644 --- a/app/Access/Oidc/OidcJwtWithClaims.php +++ b/app/Access/Oidc/OidcJwtWithClaims.php @@ -131,8 +131,6 @@ protected function validateTokenSignature(): void } }, $this->keys); - $parsedKeys = array_filter($parsedKeys); - $contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1]; /** @var OidcJwtSigningKey $parsedKey */ foreach ($parsedKeys as $parsedKey) { diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 3faa76657b6..b49eb456488 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -3,6 +3,7 @@ namespace BookStack\Activity\Models; use BookStack\App\Model; +use BookStack\Entities\Models\Page; use BookStack\Permissions\Models\JointPermission; use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\HasCreatorAndUpdater; @@ -40,6 +41,9 @@ class Comment extends Model implements Loggable, OwnableInterface /** * Get the entity that this comment belongs to. + * It's only pages right now hence the typing below. + * Would need a deeper audit if that changes as many areas assume this is always a page. + * @return MorphTo */ public function entity(): MorphTo { @@ -55,7 +59,9 @@ public function entity(): MorphTo // Ultimately, we could just align the method name to 'commentable' but that would be a potential // breaking change and not really worthwhile in a patch due to the risk of creating extra problems. - return $this->morphTo(null, 'commentable_type', 'commentable_id'); + /** @var MorphTo $relation */ + $relation = $this->morphTo(null, 'commentable_type', 'commentable_id'); + return $relation; } /** diff --git a/app/Api/ApiDocsGenerator.php b/app/Api/ApiDocsGenerator.php index 53cb2890a7e..8d1be79c88f 100644 --- a/app/Api/ApiDocsGenerator.php +++ b/app/Api/ApiDocsGenerator.php @@ -138,8 +138,8 @@ protected function getValidationAsString($validation): string return $validation; } - if (is_object($validation) && method_exists($validation, '__toString')) { - return strval($validation); + if (is_object($validation) && $validation instanceof \Stringable) { + return $validation->__toString(); } if ($validation instanceof Password) { diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index 671e5e1df74..9b0f7001e81 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -7,6 +7,7 @@ use BookStack\Theming\ThemeViews; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; +use Illuminate\View\FileViewFinder; class ThemeServiceProvider extends ServiceProvider { @@ -27,7 +28,11 @@ public function boot(): void // Boot up the theme system $themeService = $this->app->make(ThemeService::class); $viewFactory = $this->app->make('view'); - $themeViews = new ThemeViews($viewFactory->getFinder()); + $viewFinder = $viewFactory->getFinder(); + if (!($viewFinder instanceof FileViewFinder)) { + throw new \Exception('Only the file view finder is supported for the theme system'); + } + $themeViews = new ThemeViews($viewFinder); // Use a custom include so that we can insert theme views before/after includes. // This is done, even if no theme is active, so that view caching does not create problems diff --git a/app/App/helpers.php b/app/App/helpers.php index 8f210ecafd4..45a84da8db7 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -6,6 +6,7 @@ use BookStack\Permissions\Permission; use BookStack\Permissions\PermissionApplicator; use BookStack\Settings\SettingService; +use BookStack\Users\Models\OwnableInterface; use BookStack\Users\Models\User; /** @@ -40,7 +41,7 @@ function user(): User * Check if the current user has a permission. If an ownable element * is passed in the jointPermissions are checked against that particular item. */ -function userCan(string|Permission $permission, ?Model $ownable = null): bool +function userCan(string|Permission $permission, (Model&OwnableInterface)|null $ownable = null): bool { if (is_null($ownable)) { return user()->can($permission); diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 27cfccaa836..296bd3a6621 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -46,7 +46,7 @@ * @property int|null $created_by * @property int|null $updated_by * @property int|null $owned_by - * @property Collection $tags + * @property Collection $tags * * @method static Entity|Builder visible() * @method static Builder withLastView() diff --git a/app/Entities/Queries/QueryPopular.php b/app/Entities/Queries/QueryPopular.php index 065ae82ef82..1782070de86 100644 --- a/app/Entities/Queries/QueryPopular.php +++ b/app/Entities/Queries/QueryPopular.php @@ -6,6 +6,7 @@ use BookStack\Entities\EntityProvider; use BookStack\Entities\Tools\MixedEntityListLoader; use BookStack\Permissions\PermissionApplicator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; @@ -20,6 +21,7 @@ public function __construct( public function run(int $count, int $page, array $filterModels): Collection { + /** @var Builder $query */ $query = $this->permissions ->restrictEntityRelationQuery(View::query(), 'views', 'viewable_id', 'viewable_type') ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count')) diff --git a/app/Entities/Queries/QueryRecentlyViewed.php b/app/Entities/Queries/QueryRecentlyViewed.php index f28b8f8652f..8ba1a57badd 100644 --- a/app/Entities/Queries/QueryRecentlyViewed.php +++ b/app/Entities/Queries/QueryRecentlyViewed.php @@ -5,6 +5,7 @@ use BookStack\Activity\Models\View; use BookStack\Entities\Tools\MixedEntityListLoader; use BookStack\Permissions\PermissionApplicator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; class QueryRecentlyViewed @@ -22,6 +23,7 @@ public function run(int $count, int $page): Collection return collect(); } + /** @var Builder $query */ $query = $this->permissions->restrictEntityRelationQuery( View::query(), 'views', diff --git a/app/Entities/Queries/QueryTopFavourites.php b/app/Entities/Queries/QueryTopFavourites.php index 6340e35ef18..2719e94bc8b 100644 --- a/app/Entities/Queries/QueryTopFavourites.php +++ b/app/Entities/Queries/QueryTopFavourites.php @@ -5,6 +5,7 @@ use BookStack\Activity\Models\Favourite; use BookStack\Entities\Tools\MixedEntityListLoader; use BookStack\Permissions\PermissionApplicator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; class QueryTopFavourites @@ -22,6 +23,7 @@ public function run(int $count, int $skip = 0) return collect(); } + /** @var Builder $query */ $query = $this->permissions ->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type') ->select('favourites.*') diff --git a/app/Http/Controller.php b/app/Http/Controller.php index 796505795e5..09c38ee6123 100644 --- a/app/Http/Controller.php +++ b/app/Http/Controller.php @@ -7,6 +7,7 @@ use BookStack\Exceptions\NotifyException; use BookStack\Facades\Activity; use BookStack\Permissions\Permission; +use BookStack\Users\Models\OwnableInterface; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Http\JsonResponse; @@ -80,7 +81,7 @@ protected function preventGuestAccess(): void /** * Check the current user's permissions against an ownable item otherwise throw an exception. */ - protected function checkOwnablePermission(string|Permission $permission, Model $ownable, string $redirectLocation = '/'): void + protected function checkOwnablePermission(string|Permission $permission, Model&OwnableInterface $ownable, string $redirectLocation = '/'): void { if (!userCan($permission, $ownable)) { $this->showPermissionError($redirectLocation); diff --git a/app/Util/DatabaseTransaction.php b/app/Util/DatabaseTransaction.php index e36bd2ef310..2e57b6479a1 100644 --- a/app/Util/DatabaseTransaction.php +++ b/app/Util/DatabaseTransaction.php @@ -3,6 +3,7 @@ namespace BookStack\Util; use Closure; +use Illuminate\Database\Connection; use Illuminate\Support\Facades\DB; use Throwable; @@ -24,7 +25,7 @@ class DatabaseTransaction { /** - * @param (Closure(static): TReturn) $callback + * @param (Closure(Connection): TReturn) $callback */ public function __construct( protected Closure $callback diff --git a/phpstan.neon.dist b/phpstan.neon.dist index bab28ea0eb3..54403adfefc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,7 +7,7 @@ parameters: - app # The level 8 is the highest level - level: 4 + level: 5 phpVersion: min: 80200 From 03c9ddd14fed16177ebe59adaef36fd6eb5bded4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 31 May 2026 11:12:23 +0100 Subject: [PATCH 02/37] Maintenance: Further PHPStan level 5 type fixes --- app/Access/Oidc/OidcService.php | 1 + app/Activity/ActivityQueries.php | 3 ++ app/Activity/Tools/WebhookFormatter.php | 8 +++- .../Controllers/PageApiController.php | 20 ++++++---- app/Entities/Controllers/PageController.php | 5 ++- .../Controllers/PageRevisionController.php | 2 +- .../Tools/Markdown/CustomListItemRenderer.php | 6 ++- app/Entities/Tools/PageContent.php | 37 +++++++++++++------ app/Entities/Tools/PageIncludeParser.php | 4 +- app/Entities/Tools/SlugGenerator.php | 2 +- 10 files changed, 59 insertions(+), 29 deletions(-) diff --git a/app/Access/Oidc/OidcService.php b/app/Access/Oidc/OidcService.php index a84bd320513..19120e2d3c3 100644 --- a/app/Access/Oidc/OidcService.php +++ b/app/Access/Oidc/OidcService.php @@ -80,6 +80,7 @@ public function processAuthorizeResponse(?string $authorizationCode): User $provider->setPkceCode($pkceCode); // Try to exchange authorization code for access token + /** @var OidcAccessToken $accessToken */ $accessToken = $provider->getAccessToken('authorization_code', [ 'code' => $authorizationCode, ]); diff --git a/app/Activity/ActivityQueries.php b/app/Activity/ActivityQueries.php index d5b047937fb..a3383e80933 100644 --- a/app/Activity/ActivityQueries.php +++ b/app/Activity/ActivityQueries.php @@ -11,6 +11,7 @@ use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\Relation; @@ -27,6 +28,7 @@ public function __construct( */ public function latest(int $count = 20, int $page = 0): array { + /** @var Collection $activityList */ $activityList = $this->permissions ->restrictEntityRelationQuery(Activity::query(), 'activities', 'loggable_id', 'loggable_type') ->orderBy('created_at', 'desc') @@ -83,6 +85,7 @@ public function entityActivity(Entity $entity, int $count = 20, int $page = 1): */ public function userActivity(User $user, int $count = 20, int $page = 0): array { + /** @var Collection $activityList */ $activityList = $this->permissions ->restrictEntityRelationQuery(Activity::query(), 'activities', 'loggable_id', 'loggable_type') ->orderBy('created_at', 'desc') diff --git a/app/Activity/Tools/WebhookFormatter.php b/app/Activity/Tools/WebhookFormatter.php index cb4e9cb0a9c..111efd5d498 100644 --- a/app/Activity/Tools/WebhookFormatter.php +++ b/app/Activity/Tools/WebhookFormatter.php @@ -73,13 +73,17 @@ public function addDefaultModelFormatters(): void // Load entity owner, creator, updater details $this->addModelFormatter( fn ($event, $model) => ($model instanceof Entity), - fn ($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy']) + function ($model) { + $model->load(['ownedBy', 'createdBy', 'updatedBy']); + } ); // Load revision detail for page update and create events $this->addModelFormatter( fn ($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)), - fn ($model) => $model->load('currentRevision') + function ($model) { + $model->load('currentRevision'); + } ); } diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 38042e67058..ca8989f0764 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -1,5 +1,7 @@ queries->visibleForList() ->addSelect(['created_by', 'updated_by', 'revision_count', 'editor']); @@ -69,7 +73,7 @@ public function list() * Any images included via base64 data URIs will be extracted and saved as gallery * images against the page during upload. */ - public function create(Request $request) + public function create(Request $request): JsonResponse { $this->validate($request, $this->rules['create']); @@ -102,9 +106,9 @@ public function create(Request $request) * Comments for the page are provided in a tree-structure representing the hierarchy of top-level * comments and replies, for both archived and active comments. */ - public function read(string $id) + public function read(string $id): JsonResponse { - $page = $this->queries->findVisibleByIdOrFail($id); + $page = $this->queries->findVisibleByIdOrFail(intval($id)); $page = $page->forJsonDisplay(); $commentTree = (new CommentTree($page)); @@ -124,11 +128,11 @@ public function read(string $id) * Providing a 'book_id' or 'chapter_id' property will essentially move * the page into that parent element if you have permissions to do so. */ - public function update(Request $request, string $id) + public function update(Request $request, string $id): JsonResponse { $requestData = $this->validate($request, $this->rules['update']); - $page = $this->queries->findVisibleByIdOrFail($id); + $page = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission(Permission::PageUpdate, $page); $parent = null; @@ -161,9 +165,9 @@ public function update(Request $request, string $id) * Delete a page. * This will typically send the page to the recycle bin. */ - public function delete(string $id) + public function delete(string $id): Response { - $page = $this->queries->findVisibleByIdOrFail($id); + $page = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission(Permission::PageDelete, $page); $this->pageRepo->destroy($page); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 82edfbc2763..dd655e7abb3 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -25,6 +25,7 @@ use BookStack\Util\HtmlContentFilterConfig; use Exception; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; use Throwable; @@ -358,8 +359,8 @@ public function destroyDraft(string $bookSlug, int $pageId) */ public function showRecentlyUpdated() { - $visibleBelongsScope = function (BelongsTo $query) { - $query->scopes('visible'); + $visibleBelongsScope = function (Relation $relation): void { + $relation->scopes('visible'); }; $pages = $this->queries->visibleForList() diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index cc6b79bfe45..801aeed3ed3 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -177,7 +177,7 @@ public function destroy(string $bookSlug, string $pageSlug, int $revId) */ public function destroyUserDraft(string $pageId) { - $page = $this->pageQueries->findVisibleByIdOrFail($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail(intval($pageId)); $this->revisionRepo->deleteDraftsForCurrentUser($page); return response('', 200); diff --git a/app/Entities/Tools/Markdown/CustomListItemRenderer.php b/app/Entities/Tools/Markdown/CustomListItemRenderer.php index 0c506d7f9b5..14cdaa2eaa4 100644 --- a/app/Entities/Tools/Markdown/CustomListItemRenderer.php +++ b/app/Entities/Tools/Markdown/CustomListItemRenderer.php @@ -25,9 +25,13 @@ public function __construct() */ public function render(Node $node, ChildNodeRendererInterface $childRenderer) { + if (!($node instanceof ListItem)) { + return null; + } + $listItem = $this->baseRenderer->render($node, $childRenderer); - if ($node instanceof ListItem && $this->startsTaskListItem($node) && $listItem instanceof HtmlElement) { + if ($this->startsTaskListItem($node) && $listItem instanceof HtmlElement) { $listItem->setAttribute('class', 'task-list-item'); } diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 9fb4596f5a0..c66dcbe4151 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -421,28 +421,41 @@ public function getNavigation(string $htmlContent): array */ protected function headerNodesToLevelList(DOMNodeList $nodeList): array { - $tree = collect($nodeList)->map(function (DOMElement $header) { + $minLevel = 6; + + $headerDetails = array_map(function (DOMNode $header) use (&$minLevel) { + if (!$header instanceof DOMElement) { + return null; + } + $text = trim(str_replace("\xc2\xa0", ' ', $header->nodeValue)); $text = mb_substr($text, 0, 100); + if (empty($text)) { + return null; + } + + $level = intval(str_replace('h', '', $header->nodeName)); + if ($level < $minLevel) { + $minLevel = $level; + } + return [ 'nodeName' => strtolower($header->nodeName), - 'level' => intval(str_replace('h', '', $header->nodeName)), + 'level' => $level, 'link' => '#' . $header->getAttribute('id'), 'text' => $text, ]; - })->filter(function ($header) { - return mb_strlen($header['text']) > 0; - }); + }, [...$nodeList]); - // Shift headers if only smaller headers have been used - $levelChange = ($tree->pluck('level')->min() - 1); - $tree = $tree->map(function ($header) use ($levelChange) { - $header['level'] -= ($levelChange); + $filtered = array_values(array_filter($headerDetails)); - return $header; - }); + // Shift headers if only smaller headers have been used + $levelChange = ($minLevel - 1); + foreach ($filtered as $index => $header) { + $filtered[$index]['level'] -= $levelChange; + } - return $tree->toArray(); + return $filtered; } } diff --git a/app/Entities/Tools/PageIncludeParser.php b/app/Entities/Tools/PageIncludeParser.php index af7ed4fc6a1..16917f41c8e 100644 --- a/app/Entities/Tools/PageIncludeParser.php +++ b/app/Entities/Tools/PageIncludeParser.php @@ -192,10 +192,10 @@ protected function splitNodeAtChildNode(DOMElement $parentNode, DOMNode $domNode /** * Get the parent paragraph of the given node, if existing. */ - protected function getParentParagraph(DOMNode $parent): ?DOMNode + protected function getParentParagraph(DOMNode $parent): ?DOMElement { do { - if (strtolower($parent->nodeName) === 'p') { + if (strtolower($parent->nodeName) === 'p' && $parent instanceof DOMElement) { return $parent; } diff --git a/app/Entities/Tools/SlugGenerator.php b/app/Entities/Tools/SlugGenerator.php index 6eec84a91c1..ac003b97a8f 100644 --- a/app/Entities/Tools/SlugGenerator.php +++ b/app/Entities/Tools/SlugGenerator.php @@ -52,7 +52,7 @@ protected function formatNameAsSlug(string $name): string { $slug = Str::slug($name); if ($slug === '') { - $slug = substr(md5(rand(1, 500)), 0, 5); + $slug = substr(md5(strval(rand(1, 500))), 0, 5); } return $slug; From fa3b7f77b9d430304ac5683a0db374adca2f85d3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 31 May 2026 15:42:54 +0100 Subject: [PATCH 03/37] Maintenance: Addressed all phpstan level 5 issues --- app/Entities/Models/Book.php | 8 ++++---- app/Entities/Models/Page.php | 4 ++-- app/Exports/ImportRepo.php | 5 +++++ app/Exports/ZipExports/ZipImportRunner.php | 2 +- app/Permissions/JointPermissionBuilder.php | 3 +++ app/References/Reference.php | 2 +- app/References/ReferenceFetcher.php | 4 ++++ app/Search/SearchRunner.php | 4 ++++ app/Settings/SettingService.php | 6 +++--- app/Sorting/SortRule.php | 14 ++++++++++++++ app/Uploads/AttachmentService.php | 2 +- app/Users/Controllers/RoleApiController.php | 2 +- app/Users/Controllers/RoleController.php | 6 +++--- app/Users/Controllers/UserApiController.php | 8 ++++---- 14 files changed, 50 insertions(+), 20 deletions(-) diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 10f04695a5e..abb081d712f 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -20,10 +20,10 @@ * @property ?int $image_id * @property ?int $default_template_id * @property ?int $sort_rule_id - * @property \Illuminate\Database\Eloquent\Collection $chapters - * @property \Illuminate\Database\Eloquent\Collection $pages - * @property \Illuminate\Database\Eloquent\Collection $directPages - * @property \Illuminate\Database\Eloquent\Collection $shelves + * @property \Illuminate\Database\Eloquent\Collection $chapters + * @property \Illuminate\Database\Eloquent\Collection $pages + * @property \Illuminate\Database\Eloquent\Collection $directPages + * @property \Illuminate\Database\Eloquent\Collection $shelves * @property ?SortRule $sortRule */ class Book extends Entity implements HasDescriptionInterface, HasCoverInterface, HasDefaultTemplateInterface diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index d3a392da6fa..c38f33af3ca 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -24,8 +24,8 @@ * @property int $revision_count * @property string $editor * @property Chapter|null $chapter - * @property Collection $attachments - * @property Collection $revisions + * @property Collection $attachments + * @property Collection $revisions * @property PageRevision $currentRevision */ class Page extends BookChild diff --git a/app/Exports/ImportRepo.php b/app/Exports/ImportRepo.php index 79db69fca8f..ed631409be3 100644 --- a/app/Exports/ImportRepo.php +++ b/app/Exports/ImportRepo.php @@ -3,6 +3,8 @@ namespace BookStack\Exports; use BookStack\Activity\ActivityType; +use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\FileUploadException; @@ -119,6 +121,9 @@ public function runImport(Import $import, ?string $parent = null): Entity $parentModel = null; if ($import->type === 'page' || $import->type === 'chapter') { $parentModel = $parent ? $this->entityQueries->findVisibleByStringIdentifier($parent) : null; + if ($parentModel && !($parentModel instanceof Book || $parentModel instanceof Chapter)) { + throw new ZipImportException(['Selected parent is not a book or chapter']); + } } DB::beginTransaction(); diff --git a/app/Exports/ZipExports/ZipImportRunner.php b/app/Exports/ZipExports/ZipImportRunner.php index 9fa7dec3afe..37f6c0b8d33 100644 --- a/app/Exports/ZipExports/ZipImportRunner.php +++ b/app/Exports/ZipExports/ZipImportRunner.php @@ -48,7 +48,7 @@ public function __construct( * Returns the top-level entity item which was imported. * @throws ZipImportException */ - public function run(Import $import, ?Entity $parent = null): Entity + public function run(Import $import, Book|Chapter|null $parent = null): Entity { $zipPath = $this->getZipPath($import); $reader = new ZipExportReader($zipPath); diff --git a/app/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 94f18916d4a..f2159375df1 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -101,6 +101,7 @@ public function rebuildForRole(Role $role) /** * Get a query for fetching a book with its children. + * @return Builder */ protected function bookFetchQuery(): Builder { @@ -117,9 +118,11 @@ protected function bookFetchQuery(): Builder /** * Build joint permissions for the given book and role combinations. + * @param EloquentCollection $books */ protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false): void { + /** @var EloquentCollection $entities */ $entities = clone $books; /** @var Book $book */ diff --git a/app/References/Reference.php b/app/References/Reference.php index df8a3a78932..602e19eab59 100644 --- a/app/References/Reference.php +++ b/app/References/Reference.php @@ -2,8 +2,8 @@ namespace BookStack\References; +use BookStack\App\Model; use BookStack\Permissions\Models\JointPermission; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; diff --git a/app/References/ReferenceFetcher.php b/app/References/ReferenceFetcher.php index 8588c6e2c8e..0c76b46982c 100644 --- a/app/References/ReferenceFetcher.php +++ b/app/References/ReferenceFetcher.php @@ -22,6 +22,7 @@ public function __construct( */ public function getReferencesToEntity(Entity $entity, bool $withContents = false): Collection { + /** @var Collection $references */ $references = $this->queryReferencesToEntity($entity)->get(); $this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from', false, $withContents); @@ -37,6 +38,9 @@ public function getReferenceCountToEntity(Entity $entity): int return $this->queryReferencesToEntity($entity)->count(); } + /** + * @return Builder + */ protected function queryReferencesToEntity(Entity $entity): Builder { $baseQuery = Reference::query() diff --git a/app/Search/SearchRunner.php b/app/Search/SearchRunner.php index 3912541723f..c2f3b96c9e6 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -4,6 +4,7 @@ use BookStack\Entities\EntityProvider; use BookStack\Entities\Models\Entity; +use BookStack\Entities\Models\EntityTable; use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Tools\EntityHydrator; use BookStack\Permissions\PermissionApplicator; @@ -90,6 +91,8 @@ public function searchChapter(int $chapterId, string $searchString): Collection /** * Get a page of result data from the given query based on the provided page parameters. + * @param EloquentBuilder $query + * @return Collection */ protected function getPageOfDataFromQuery(EloquentBuilder $query, int $page, int $count): Collection { @@ -106,6 +109,7 @@ protected function getPageOfDataFromQuery(EloquentBuilder $query, int $page, int /** * Create a search query for an entity. * @param string[] $entityTypes + * @return EloquentBuilder */ protected function buildQuery(SearchOptions $searchOpts, array $entityTypes): EloquentBuilder { diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index e0b13618012..d35f7711795 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -243,9 +243,9 @@ public function putForCurrentUser(string $key, string $value): bool /** * Convert a setting key into a user-specific key. */ - protected function userKey(string $userId, string $key = ''): string + protected function userKey(int $userId, string $key = ''): string { - return 'user:' . $userId . ':' . $key; + return 'user:' . strval($userId) . ':' . $key; } /** @@ -267,7 +267,7 @@ public function remove(string $key): void /** * Delete settings for a given user id. */ - public function deleteUserSettings(string $userId): void + public function deleteUserSettings(int $userId): void { Setting::query() ->where('setting_key', 'like', $this->userKey($userId) . '%') diff --git a/app/Sorting/SortRule.php b/app/Sorting/SortRule.php index bf53365a201..ffe2d411534 100644 --- a/app/Sorting/SortRule.php +++ b/app/Sorting/SortRule.php @@ -38,21 +38,35 @@ public function setOperations(array $options): void $this->sequence = implode(',', $values); } + /** + * @inheritDoc + */ public function logDescriptor(): string { return "({$this->id}) {$this->name}"; } + /** + * Get the URL to where this rule can be managed. + */ public function getUrl(): string { return url("/settings/sorting/rules/{$this->id}"); } + /** + * Get the books which are specifically set to use this sort rule. + * @return HasMany + */ public function books(): HasMany { return $this->hasMany(Book::class, 'entity_container_data.sort_rule_id', 'id'); } + /** + * Get all the available sort rules, ordered by name, with the number of books using each. + * @return Collection + */ public static function allByName(): Collection { return static::query() diff --git a/app/Uploads/AttachmentService.php b/app/Uploads/AttachmentService.php index dabd537292f..3de84bdd0a8 100644 --- a/app/Uploads/AttachmentService.php +++ b/app/Uploads/AttachmentService.php @@ -102,7 +102,7 @@ public function saveNewFromLink(string $name, string $link, int $page_id): Attac /** * Updates the ordering for a listing of attached files. */ - public function updateFileOrderWithinPage(array $attachmentOrder, string $pageId) + public function updateFileOrderWithinPage(array $attachmentOrder, int $pageId) { foreach ($attachmentOrder as $index => $attachmentId) { Attachment::query()->where('uploaded_to', '=', $pageId) diff --git a/app/Users/Controllers/RoleApiController.php b/app/Users/Controllers/RoleApiController.php index 93ecc549bb4..6f0c59651a5 100644 --- a/app/Users/Controllers/RoleApiController.php +++ b/app/Users/Controllers/RoleApiController.php @@ -87,7 +87,7 @@ public function create(Request $request) */ public function read(string $id) { - $role = $this->permissionsRepo->getRoleById($id); + $role = $this->permissionsRepo->getRoleById(intval($id)); $this->singleFormatter($role); return response()->json($role); diff --git a/app/Users/Controllers/RoleController.php b/app/Users/Controllers/RoleController.php index b9f06dace84..56a6c7416ca 100644 --- a/app/Users/Controllers/RoleController.php +++ b/app/Users/Controllers/RoleController.php @@ -94,7 +94,7 @@ public function store(Request $request) public function edit(string $id) { $this->checkPermission(Permission::UserRolesManage); - $role = $this->permissionsRepo->getRoleById($id); + $role = $this->permissionsRepo->getRoleById(intval($id)); $this->setPageTitle(trans('settings.role_edit')); @@ -129,7 +129,7 @@ public function update(Request $request, string $id) public function showDelete(string $id) { $this->checkPermission(Permission::UserRolesManage); - $role = $this->permissionsRepo->getRoleById($id); + $role = $this->permissionsRepo->getRoleById(intval($id)); $roles = $this->permissionsRepo->getAllRolesExcept($role); $blankRole = $role->newInstance(['display_name' => trans('settings.role_delete_no_migration')]); $roles->prepend($blankRole); @@ -151,7 +151,7 @@ public function delete(Request $request, string $id) try { $migrateRoleId = intval($request->input('migrate_role_id') ?: "0"); - $this->permissionsRepo->deleteRole($id, $migrateRoleId); + $this->permissionsRepo->deleteRole(intval($id), $migrateRoleId); } catch (PermissionsException $e) { $this->showErrorNotification($e->getMessage()); diff --git a/app/Users/Controllers/UserApiController.php b/app/Users/Controllers/UserApiController.php index ebc17e262f3..07eefadc7b1 100644 --- a/app/Users/Controllers/UserApiController.php +++ b/app/Users/Controllers/UserApiController.php @@ -110,7 +110,7 @@ public function create(Request $request) */ public function read(string $id) { - $user = $this->userRepo->getById($id); + $user = $this->userRepo->getById(intval($id)); $this->singleFormatter($user); return response()->json($user); @@ -124,8 +124,8 @@ public function read(string $id) */ public function update(Request $request, string $id) { - $data = $this->validate($request, $this->rules($id)['update']); - $user = $this->userRepo->getById($id); + $data = $this->validate($request, $this->rules(intval($id))['update']); + $user = $this->userRepo->getById(intval($id)); $this->userRepo->update($user, $data, userCan(Permission::UsersManage)); $this->singleFormatter($user); @@ -140,7 +140,7 @@ public function update(Request $request, string $id) */ public function delete(Request $request, string $id) { - $user = $this->userRepo->getById($id); + $user = $this->userRepo->getById(intval($id)); $newOwnerId = $request->input('migrate_ownership_id', null); $this->userRepo->destroy($user, $newOwnerId); From 26a3cb8b7e36a421c4491ad987e053d96ee84075 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 6 Aug 2026 15:32:17 +0100 Subject: [PATCH 04/37] Views: Started implementation of new sidebar back-end --- .../Providers/ViewTweaksServiceProvider.php | 2 + app/Entities/Controllers/BookController.php | 2 - app/View/SidebarSection.php | 16 ++++++ app/View/SidebarSectionInterface.php | 20 ++++++++ app/View/SidebarSectionManager.php | 51 +++++++++++++++++++ .../SidebarSections/BooksIndexRecents.php | 26 ++++++++++ resources/views/books/index.blade.php | 4 +- 7 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 app/View/SidebarSection.php create mode 100644 app/View/SidebarSectionInterface.php create mode 100644 app/View/SidebarSectionManager.php create mode 100644 app/View/SidebarSections/BooksIndexRecents.php diff --git a/app/App/Providers/ViewTweaksServiceProvider.php b/app/App/Providers/ViewTweaksServiceProvider.php index 6771e513fa6..d2d45abcc5d 100644 --- a/app/App/Providers/ViewTweaksServiceProvider.php +++ b/app/App/Providers/ViewTweaksServiceProvider.php @@ -4,6 +4,7 @@ use BookStack\Entities\BreadcrumbsViewComposer; use BookStack\Util\DateFormatter; +use BookStack\View\SidebarSectionManager; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\View; @@ -33,6 +34,7 @@ public function boot(): void // View Globals View::share('dates', $this->app->make(DateFormatter::class)); + View::share('sidebar', $this->app->make(SidebarSectionManager::class)); // Custom blade view directives Blade::directive('icon', function ($expression) { diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index 98470d91ce8..9fa64d2934c 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -53,7 +53,6 @@ public function index(Request $request) $books = $this->queries->visibleForListWithCover() ->orderBy($listOptions->getSort(), $listOptions->getOrder()) ->paginate(setting()->getInteger('lists-page-count-books', 18, 1, 1000)); - $recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->take(4)->get() : false; $popular = $this->queries->popularForList()->take(4)->get(); $new = $this->queries->visibleForList()->orderBy('created_at', 'desc')->take(4)->get(); @@ -63,7 +62,6 @@ public function index(Request $request) return view('books.index', [ 'books' => $books, - 'recents' => $recents, 'popular' => $popular, 'new' => $new, 'view' => $view, diff --git a/app/View/SidebarSection.php b/app/View/SidebarSection.php new file mode 100644 index 00000000000..68e1aa29193 --- /dev/null +++ b/app/View/SidebarSection.php @@ -0,0 +1,16 @@ +view; + } +} diff --git a/app/View/SidebarSectionInterface.php b/app/View/SidebarSectionInterface.php new file mode 100644 index 00000000000..84032ab6174 --- /dev/null +++ b/app/View/SidebarSectionInterface.php @@ -0,0 +1,20 @@ + + */ + public function withData(array $viewData, Request $request): array; +} diff --git a/app/View/SidebarSectionManager.php b/app/View/SidebarSectionManager.php new file mode 100644 index 00000000000..82af3721ec4 --- /dev/null +++ b/app/View/SidebarSectionManager.php @@ -0,0 +1,51 @@ +[]>> + */ + protected array $sectionByLocation = [ + 'books-index' => [ + 'left' => [ + BooksIndexRecents::class, + ] + ], + ]; + + /** + * Register a sidebar section to be displayed at the given location and position. + * @param class-string $sectionClass + */ + public function register(string $location, string $position, string $sectionClass): void + { + if (!isset($this->sectionByLocation[$location])) { + $this->sectionByLocation[$location] = []; + } + + if (!isset($this->sectionByLocation[$location][$position])) { + $this->sectionByLocation[$location][$position] = []; + } + + $this->sectionByLocation[$location][$position][] = $sectionClass; + } + + /** + * Get all sidebar sections registered for a given location. + * + * @return SidebarSectionInterface[] + * @throws BindingResolutionException + */ + public function getSectionsForLocation(string $location, string $position): array + { + $sections = array_unique($this->sectionByLocation[$location][$position] ?? []); + return array_map(function (string $className) { + return app()->make($className); + }, $sections); + } +} diff --git a/app/View/SidebarSections/BooksIndexRecents.php b/app/View/SidebarSections/BooksIndexRecents.php new file mode 100644 index 00000000000..485e6d431fc --- /dev/null +++ b/app/View/SidebarSections/BooksIndexRecents.php @@ -0,0 +1,26 @@ +isGuest(); + $recents = $userSignedIn ? $this->queries->recentlyViewedForCurrentUser()->take(4)->get() : null; + return [ + 'recents' => $recents, + ]; + } +} diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 660c008dfb1..60152410cb9 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -5,7 +5,9 @@ @stop @section('left') - @include('books.parts.index-sidebar-section-recents', ['recents' => $recents]) + @foreach($sidebar->getSectionsForLocation('books-index', 'left') as $section) + @include($section->getView(), $section->withData(get_defined_vars(), request())) + @endforeach @include('books.parts.index-sidebar-section-popular', ['popular' => $popular]) @include('books.parts.index-sidebar-section-new', ['new' => $new]) @stop From dead0bfb5fd5b725230a5020973ddd4fa9560a95 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 7 Aug 2026 08:32:27 +0100 Subject: [PATCH 05/37] Views: Extracted books index/show sidebar sections --- app/Entities/Controllers/BookController.php | 15 +-------- app/View/SidebarSectionManager.php | 28 +++++++++++++++- .../SidebarSections/BooksIndexActions.php | 18 +++++++++++ app/View/SidebarSections/BooksIndexNew.php | 29 +++++++++++++++++ .../SidebarSections/BooksIndexPopular.php | 24 ++++++++++++++ app/View/SidebarSections/BooksShowActions.php | 24 ++++++++++++++ .../SidebarSections/BooksShowActivity.php | 28 ++++++++++++++++ app/View/SidebarSections/BooksShowDetails.php | 32 +++++++++++++++++++ .../SidebarSections/BooksShowSearchForm.php | 18 +++++++++++ app/View/SidebarSections/BooksShowShelves.php | 23 +++++++++++++ app/View/SidebarSections/BooksShowTags.php | 18 +++++++++++ resources/views/books/index.blade.php | 8 ++--- .../show-sidebar-section-details.blade.php | 2 +- .../show-sidebar-section-shelves.blade.php | 4 +-- resources/views/books/show.blade.php | 13 +++----- .../views/common/sidebar-sections.blade.php | 11 +++++++ 16 files changed, 262 insertions(+), 33 deletions(-) create mode 100644 app/View/SidebarSections/BooksIndexActions.php create mode 100644 app/View/SidebarSections/BooksIndexNew.php create mode 100644 app/View/SidebarSections/BooksIndexPopular.php create mode 100644 app/View/SidebarSections/BooksShowActions.php create mode 100644 app/View/SidebarSections/BooksShowActivity.php create mode 100644 app/View/SidebarSections/BooksShowDetails.php create mode 100644 app/View/SidebarSections/BooksShowSearchForm.php create mode 100644 app/View/SidebarSections/BooksShowShelves.php create mode 100644 app/View/SidebarSections/BooksShowTags.php create mode 100644 resources/views/common/sidebar-sections.blade.php diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index 9fa64d2934c..aa4f99daa6d 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -2,10 +2,8 @@ namespace BookStack\Entities\Controllers; -use BookStack\Activity\ActivityQueries; use BookStack\Activity\ActivityType; use BookStack\Activity\Models\View; -use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Queries\EntityQueries; @@ -19,7 +17,6 @@ use BookStack\Facades\Activity; use BookStack\Http\Controller; use BookStack\Permissions\Permission; -use BookStack\References\ReferenceFetcher; use BookStack\Util\DatabaseTransaction; use BookStack\Util\SimpleListOptions; use Illuminate\Http\Request; @@ -34,7 +31,6 @@ public function __construct( protected BookQueries $queries, protected EntityQueries $entityQueries, protected BookshelfQueries $shelfQueries, - protected ReferenceFetcher $referenceFetcher, ) { } @@ -53,8 +49,6 @@ public function index(Request $request) $books = $this->queries->visibleForListWithCover() ->orderBy($listOptions->getSort(), $listOptions->getOrder()) ->paginate(setting()->getInteger('lists-page-count-books', 18, 1, 1000)); - $popular = $this->queries->popularForList()->take(4)->get(); - $new = $this->queries->visibleForList()->orderBy('created_at', 'desc')->take(4)->get(); $this->shelfContext->clearShelfContext(); @@ -62,8 +56,6 @@ public function index(Request $request) return view('books.index', [ 'books' => $books, - 'popular' => $popular, - 'new' => $new, 'view' => $view, 'listOptions' => $listOptions, ]); @@ -125,7 +117,7 @@ public function store(Request $request, ?string $shelfSlug = null) /** * Display the specified book. */ - public function show(Request $request, ActivityQueries $activities, string $slug) + public function show(Request $request, string $slug) { try { $book = $this->queries->findVisibleBySlugOrFail($slug); @@ -138,7 +130,6 @@ public function show(Request $request, ActivityQueries $activities, string $slug } $bookChildren = (new BookContents($book))->getTree(true); - $bookParentShelves = $book->shelves()->scopes('visible')->get(); View::incrementFor($book); if ($request->has('shelf')) { @@ -151,10 +142,6 @@ public function show(Request $request, ActivityQueries $activities, string $slug 'book' => $book, 'current' => $book, 'bookChildren' => $bookChildren, - 'bookParentShelves' => $bookParentShelves, - 'watchOptions' => new UserEntityWatchOptions(user(), $book), - 'activity' => $activities->entityActivity($book, 20, 1), - 'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($book), ]); } diff --git a/app/View/SidebarSectionManager.php b/app/View/SidebarSectionManager.php index 82af3721ec4..d83b7afb0ea 100644 --- a/app/View/SidebarSectionManager.php +++ b/app/View/SidebarSectionManager.php @@ -2,7 +2,16 @@ namespace BookStack\View; +use BookStack\View\SidebarSections\BooksIndexActions; +use BookStack\View\SidebarSections\BooksIndexNew; +use BookStack\View\SidebarSections\BooksIndexPopular; use BookStack\View\SidebarSections\BooksIndexRecents; +use BookStack\View\SidebarSections\BooksShowActions; +use BookStack\View\SidebarSections\BooksShowActivity; +use BookStack\View\SidebarSections\BooksShowDetails; +use BookStack\View\SidebarSections\BooksShowSearchForm; +use BookStack\View\SidebarSections\BooksShowShelves; +use BookStack\View\SidebarSections\BooksShowTags; use Illuminate\Contracts\Container\BindingResolutionException; class SidebarSectionManager @@ -14,7 +23,24 @@ class SidebarSectionManager 'books-index' => [ 'left' => [ BooksIndexRecents::class, - ] + BooksIndexPopular::class, + BooksIndexNew::class, + ], + 'right' => [ + BooksIndexActions::class, + ], + ], + 'books-show' => [ + 'left' => [ + BooksShowSearchForm::class, + BooksShowTags::class, + BooksShowShelves::class, + BooksShowActivity::class, + ], + 'right' => [ + BooksShowDetails::class, + BooksShowActions::class, + ], ], ]; diff --git a/app/View/SidebarSections/BooksIndexActions.php b/app/View/SidebarSections/BooksIndexActions.php new file mode 100644 index 00000000000..62f856a651b --- /dev/null +++ b/app/View/SidebarSections/BooksIndexActions.php @@ -0,0 +1,18 @@ + $viewData['view'], + ]; + } +} diff --git a/app/View/SidebarSections/BooksIndexNew.php b/app/View/SidebarSections/BooksIndexNew.php new file mode 100644 index 00000000000..33a57f9d996 --- /dev/null +++ b/app/View/SidebarSections/BooksIndexNew.php @@ -0,0 +1,29 @@ +queries->visibleForList() + ->orderBy('created_at', 'desc') + ->take(4) + ->get(); + + return [ + 'new' => $new, + ]; + } +} diff --git a/app/View/SidebarSections/BooksIndexPopular.php b/app/View/SidebarSections/BooksIndexPopular.php new file mode 100644 index 00000000000..9f5cc613f91 --- /dev/null +++ b/app/View/SidebarSections/BooksIndexPopular.php @@ -0,0 +1,24 @@ + $this->queries->popularForList()->take(4)->get(), + ]; + } +} diff --git a/app/View/SidebarSections/BooksShowActions.php b/app/View/SidebarSections/BooksShowActions.php new file mode 100644 index 00000000000..0f97e625c6a --- /dev/null +++ b/app/View/SidebarSections/BooksShowActions.php @@ -0,0 +1,24 @@ + $book, + 'watchOptions' => new UserEntityWatchOptions(user(), $book), + ]; + } +} diff --git a/app/View/SidebarSections/BooksShowActivity.php b/app/View/SidebarSections/BooksShowActivity.php new file mode 100644 index 00000000000..d892c5ea730 --- /dev/null +++ b/app/View/SidebarSections/BooksShowActivity.php @@ -0,0 +1,28 @@ + $this->activityQueries->entityActivity($book, 20, 1), + ]; + } +} diff --git a/app/View/SidebarSections/BooksShowDetails.php b/app/View/SidebarSections/BooksShowDetails.php new file mode 100644 index 00000000000..bd91663b229 --- /dev/null +++ b/app/View/SidebarSections/BooksShowDetails.php @@ -0,0 +1,32 @@ +referenceFetcher->getReferenceCountToEntity($book); + + return [ + 'book' => $book, + 'watchOptions' => new UserEntityWatchOptions(user(), $book), + 'referenceCount' => $referenceCount, + ]; + } +} diff --git a/app/View/SidebarSections/BooksShowSearchForm.php b/app/View/SidebarSections/BooksShowSearchForm.php new file mode 100644 index 00000000000..8fa24134b15 --- /dev/null +++ b/app/View/SidebarSections/BooksShowSearchForm.php @@ -0,0 +1,18 @@ + trans('entities.books_search_this'), + ]; + } +} diff --git a/app/View/SidebarSections/BooksShowShelves.php b/app/View/SidebarSections/BooksShowShelves.php new file mode 100644 index 00000000000..28d2d8aa9f0 --- /dev/null +++ b/app/View/SidebarSections/BooksShowShelves.php @@ -0,0 +1,23 @@ +shelves()->scopes('visible')->get(); + + return [ + 'shelves' => $shelves, + ]; + } +} diff --git a/app/View/SidebarSections/BooksShowTags.php b/app/View/SidebarSections/BooksShowTags.php new file mode 100644 index 00000000000..351686c7f8a --- /dev/null +++ b/app/View/SidebarSections/BooksShowTags.php @@ -0,0 +1,18 @@ + $viewData['book'], + ]; + } +} diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 60152410cb9..020cb096638 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -5,13 +5,9 @@ @stop @section('left') - @foreach($sidebar->getSectionsForLocation('books-index', 'left') as $section) - @include($section->getView(), $section->withData(get_defined_vars(), request())) - @endforeach - @include('books.parts.index-sidebar-section-popular', ['popular' => $popular]) - @include('books.parts.index-sidebar-section-new', ['new' => $new]) + @include('common.sidebar-sections', ['location' => 'books-index', 'position' => 'left']) @stop @section('right') - @include('books.parts.index-sidebar-section-actions', ['view' => $view]) + @include('common.sidebar-sections', ['location' => 'books-index', 'position' => 'right']) @stop diff --git a/resources/views/books/parts/show-sidebar-section-details.blade.php b/resources/views/books/parts/show-sidebar-section-details.blade.php index 709d0ffd9a1..2c3d6d141dd 100644 --- a/resources/views/books/parts/show-sidebar-section-details.blade.php +++ b/resources/views/books/parts/show-sidebar-section-details.blade.php @@ -1,7 +1,7 @@
{{ trans('common.details') }}