diff --git a/app/Access/ExternalBaseUserProvider.php b/app/Access/ExternalBaseUserProvider.php index ef001289d..8d82d28c8 100644 --- a/app/Access/ExternalBaseUserProvider.php +++ b/app/Access/ExternalBaseUserProvider.php @@ -48,7 +48,7 @@ class ExternalBaseUserProvider implements UserProvider /** * Retrieve a user by the given credentials. */ - public function retrieveByCredentials(array $credentials): ?Authenticatable + public function retrieveByCredentials(array $credentials): ?User { return $this->userRepo->getByExternalAuthId($credentials['external_auth_id']); } diff --git a/app/Access/Guards/ExternalBaseSessionGuard.php b/app/Access/Guards/ExternalBaseSessionGuard.php index b38903182..4d263e581 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 9455d530d..f3628c897 100644 --- a/app/Access/Guards/LdapSessionGuard.php +++ b/app/Access/Guards/LdapSessionGuard.php @@ -83,6 +83,10 @@ class LdapSessionGuard extends ExternalBaseSessionGuard } } + 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 9d7eeead1..9763ab158 100644 --- a/app/Access/Oidc/OidcJwtWithClaims.php +++ b/app/Access/Oidc/OidcJwtWithClaims.php @@ -131,8 +131,6 @@ class OidcJwtWithClaims implements ProvidesClaims } }, $this->keys); - $parsedKeys = array_filter($parsedKeys); - $contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1]; /** @var OidcJwtSigningKey $parsedKey */ foreach ($parsedKeys as $parsedKey) { diff --git a/app/Access/Oidc/OidcService.php b/app/Access/Oidc/OidcService.php index a84bd3205..19120e2d3 100644 --- a/app/Access/Oidc/OidcService.php +++ b/app/Access/Oidc/OidcService.php @@ -80,6 +80,7 @@ class OidcService $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 d5b047937..a3383e809 100644 --- a/app/Activity/ActivityQueries.php +++ b/app/Activity/ActivityQueries.php @@ -11,6 +11,7 @@ use BookStack\Entities\Tools\MixedEntityListLoader; 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 @@ class ActivityQueries */ 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 @@ class ActivityQueries */ 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/Models/Comment.php b/app/Activity/Models/Comment.php index 3faa76657..b49eb4564 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 @@ class Comment extends Model implements Loggable, OwnableInterface // 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/Activity/Tools/WebhookFormatter.php b/app/Activity/Tools/WebhookFormatter.php index cb4e9cb0a..111efd5d4 100644 --- a/app/Activity/Tools/WebhookFormatter.php +++ b/app/Activity/Tools/WebhookFormatter.php @@ -73,13 +73,17 @@ class WebhookFormatter // 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/Api/ApiDocsGenerator.php b/app/Api/ApiDocsGenerator.php index 53cb2890a..8d1be79c8 100644 --- a/app/Api/ApiDocsGenerator.php +++ b/app/Api/ApiDocsGenerator.php @@ -138,8 +138,8 @@ class ApiDocsGenerator 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 671e5e1df..9b0f7001e 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -7,6 +7,7 @@ use BookStack\Theming\ThemeService; 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 @@ class ThemeServiceProvider extends ServiceProvider // 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 8f210ecaf..45a84da8d 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -6,6 +6,7 @@ use BookStack\Facades\Theme; 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/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 38042e670..ca8989f07 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 @@ class PageApiController extends ApiController * 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 @@ class PageApiController extends ApiController * 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 @@ class PageApiController extends ApiController * 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 @@ class PageApiController extends ApiController * 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 2cd9b0b16..10a46d58b 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -24,6 +24,7 @@ use BookStack\Util\HtmlContentFilter; 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; @@ -364,8 +365,8 @@ class PageController extends Controller */ 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 cc6b79bfe..801aeed3e 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -177,7 +177,7 @@ class PageRevisionController extends Controller */ 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/Models/Book.php b/app/Entities/Models/Book.php index 10f04695a..abb081d71 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -20,10 +20,10 @@ use Illuminate\Support\Collection; * @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/Entity.php b/app/Entities/Models/Entity.php index 27cfccaa8..296bd3a66 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -46,7 +46,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @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/Models/Page.php b/app/Entities/Models/Page.php index d3a392da6..c38f33af3 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -24,8 +24,8 @@ use Illuminate\Database\Eloquent\Relations\HasOne; * @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/Entities/Queries/QueryPopular.php b/app/Entities/Queries/QueryPopular.php index 065ae82ef..1782070de 100644 --- a/app/Entities/Queries/QueryPopular.php +++ b/app/Entities/Queries/QueryPopular.php @@ -6,6 +6,7 @@ use BookStack\Activity\Models\View; 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 @@ class QueryPopular 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 f28b8f865..8ba1a57ba 100644 --- a/app/Entities/Queries/QueryRecentlyViewed.php +++ b/app/Entities/Queries/QueryRecentlyViewed.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Queries; 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 @@ class QueryRecentlyViewed 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 6340e35ef..2719e94bc 100644 --- a/app/Entities/Queries/QueryTopFavourites.php +++ b/app/Entities/Queries/QueryTopFavourites.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Queries; 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 @@ class QueryTopFavourites return collect(); } + /** @var Builder $query */ $query = $this->permissions ->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type') ->select('favourites.*') diff --git a/app/Entities/Tools/Markdown/CustomListItemRenderer.php b/app/Entities/Tools/Markdown/CustomListItemRenderer.php index 0c506d7f9..14cdaa2ea 100644 --- a/app/Entities/Tools/Markdown/CustomListItemRenderer.php +++ b/app/Entities/Tools/Markdown/CustomListItemRenderer.php @@ -25,9 +25,13 @@ class CustomListItemRenderer implements NodeRendererInterface */ 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 9fb4596f5..c66dcbe41 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -421,28 +421,41 @@ class PageContent */ 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]); + + $filtered = array_values(array_filter($headerDetails)); // 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); + $levelChange = ($minLevel - 1); + foreach ($filtered as $index => $header) { + $filtered[$index]['level'] -= $levelChange; + } - return $header; - }); - - return $tree->toArray(); + return $filtered; } } diff --git a/app/Entities/Tools/PageIncludeParser.php b/app/Entities/Tools/PageIncludeParser.php index af7ed4fc6..16917f41c 100644 --- a/app/Entities/Tools/PageIncludeParser.php +++ b/app/Entities/Tools/PageIncludeParser.php @@ -192,10 +192,10 @@ class PageIncludeParser /** * 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 6eec84a91..ac003b97a 100644 --- a/app/Entities/Tools/SlugGenerator.php +++ b/app/Entities/Tools/SlugGenerator.php @@ -52,7 +52,7 @@ class SlugGenerator { $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; diff --git a/app/Exports/ImportRepo.php b/app/Exports/ImportRepo.php index 79db69fca..ed631409b 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 @@ class ImportRepo $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 7a53e01f4..41e9038ad 100644 --- a/app/Exports/ZipExports/ZipImportRunner.php +++ b/app/Exports/ZipExports/ZipImportRunner.php @@ -48,7 +48,7 @@ class ZipImportRunner * 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/Http/Controller.php b/app/Http/Controller.php index 796505795..09c38ee61 100644 --- a/app/Http/Controller.php +++ b/app/Http/Controller.php @@ -7,6 +7,7 @@ use BookStack\App\Model; 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 @@ abstract class Controller extends BaseController /** * 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/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 1788124cd..101e3a00f 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -105,6 +105,7 @@ class JointPermissionBuilder /** * Get a query for fetching a book with its children. + * @return Builder */ protected function bookFetchQuery(): Builder { @@ -121,9 +122,11 @@ class JointPermissionBuilder /** * 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 df8a3a789..602e19eab 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 8588c6e2c..0c76b4698 100644 --- a/app/References/ReferenceFetcher.php +++ b/app/References/ReferenceFetcher.php @@ -22,6 +22,7 @@ class ReferenceFetcher */ 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 @@ class ReferenceFetcher 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 d49cb439a..60d111d08 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -4,6 +4,7 @@ namespace BookStack\Search; 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 @@ class SearchRunner /** * 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 @@ class SearchRunner /** * 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 a86730c1b..e3112e9c7 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -243,9 +243,9 @@ class SettingService /** * 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; } /** @@ -275,7 +275,7 @@ class SettingService /** * 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 bf53365a2..ffe2d4115 100644 --- a/app/Sorting/SortRule.php +++ b/app/Sorting/SortRule.php @@ -38,21 +38,35 @@ class SortRule extends Model implements Loggable $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 dabd53729..3de84bdd0 100644 --- a/app/Uploads/AttachmentService.php +++ b/app/Uploads/AttachmentService.php @@ -102,7 +102,7 @@ class AttachmentService /** * 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 93ecc549b..6f0c59651 100644 --- a/app/Users/Controllers/RoleApiController.php +++ b/app/Users/Controllers/RoleApiController.php @@ -87,7 +87,7 @@ class RoleApiController extends ApiController */ 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 b9f06dace..56a6c7416 100644 --- a/app/Users/Controllers/RoleController.php +++ b/app/Users/Controllers/RoleController.php @@ -94,7 +94,7 @@ class RoleController extends Controller 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 @@ class RoleController extends Controller 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 @@ class RoleController extends Controller 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 ebc17e262..07eefadc7 100644 --- a/app/Users/Controllers/UserApiController.php +++ b/app/Users/Controllers/UserApiController.php @@ -110,7 +110,7 @@ class UserApiController extends ApiController */ 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 @@ class UserApiController extends ApiController */ 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 @@ class UserApiController extends ApiController */ 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); diff --git a/app/Util/DatabaseTransaction.php b/app/Util/DatabaseTransaction.php index e36bd2ef3..2e57b6479 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 @@ use Throwable; 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 bab28ea0e..54403adfe 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