diff --git a/app/Actions/CommentRepo.php b/app/Actions/CommentRepo.php index 8121dfc5c..8061c4542 100644 --- a/app/Actions/CommentRepo.php +++ b/app/Actions/CommentRepo.php @@ -90,8 +90,9 @@ class CommentRepo */ protected function getNextLocalId(Entity $entity): int { - $comments = $entity->comments(false)->orderBy('local_id', 'desc')->first(); + /** @var Comment $comment */ + $comment = $entity->comments(false)->orderBy('local_id', 'desc')->first(); - return ($comments->local_id ?? 0) + 1; + return ($comment->local_id ?? 0) + 1; } } diff --git a/app/Auth/Access/Guards/LdapSessionGuard.php b/app/Auth/Access/Guards/LdapSessionGuard.php index 7f6965937..078487224 100644 --- a/app/Auth/Access/Guards/LdapSessionGuard.php +++ b/app/Auth/Access/Guards/LdapSessionGuard.php @@ -94,7 +94,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard } // Attach avatar if non-existent - if (is_null($user->avatar)) { + if (!$user->avatar()->exists()) { $this->ldapService->saveAndAttachAvatar($user, $userDetails); } diff --git a/app/Auth/Access/Ldap.php b/app/Auth/Access/Ldap.php index b5c70e498..6259de0ae 100644 --- a/app/Auth/Access/Ldap.php +++ b/app/Auth/Access/Ldap.php @@ -10,14 +10,10 @@ namespace BookStack\Auth\Access; class Ldap { /** - * Connect to a LDAP server. - * - * @param string $hostName - * @param int $port - * + * Connect to an LDAP server. * @return resource */ - public function connect($hostName, $port) + public function connect(string $hostName, int $port) { return ldap_connect($hostName, $port); } @@ -26,12 +22,9 @@ class Ldap * Set the value of a LDAP option for the given connection. * * @param resource $ldapConnection - * @param int $option * @param mixed $value - * - * @return bool */ - public function setOption($ldapConnection, $option, $value) + public function setOption($ldapConnection, int $option, $value): bool { return ldap_set_option($ldapConnection, $option, $value); } @@ -47,12 +40,9 @@ class Ldap /** * Set the version number for the given ldap connection. * - * @param $ldapConnection - * @param $version - * - * @return bool + * @param resource $ldapConnection */ - public function setVersion($ldapConnection, $version) + public function setVersion($ldapConnection, int $version): bool { return $this->setOption($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, $version); } diff --git a/app/Console/Commands/ResetMfa.php b/app/Console/Commands/ResetMfa.php index 031bec04b..9074a4a46 100644 --- a/app/Console/Commands/ResetMfa.php +++ b/app/Console/Commands/ResetMfa.php @@ -49,9 +49,10 @@ class ResetMfa extends Command return 1; } - /** @var User $user */ $field = $id ? 'id' : 'email'; $value = $id ?: $email; + + /** @var User $user */ $user = User::query() ->where($field, '=', $value) ->first(); diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index abf496b44..75630832b 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -3,13 +3,14 @@ namespace BookStack\Entities\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; /** * Class Chapter. * * @property Collection $pages - * @property mixed description + * @property string $description */ class Chapter extends BookChild { @@ -22,12 +23,8 @@ class Chapter extends BookChild /** * Get the pages that this chapter contains. - * - * @param string $dir - * - * @return mixed */ - public function pages($dir = 'ASC') + public function pages(string $dir = 'ASC'): HasMany { return $this->hasMany(Page::class)->orderBy('priority', $dir); } @@ -35,7 +32,7 @@ class Chapter extends BookChild /** * Get the url of this chapter. */ - public function getUrl($path = ''): string + public function getUrl(string $path = ''): string { $parts = [ 'books', diff --git a/app/Entities/Models/Deletion.php b/app/Entities/Models/Deletion.php index dab89ce37..3face841b 100644 --- a/app/Entities/Models/Deletion.php +++ b/app/Entities/Models/Deletion.php @@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; /** - * @property Model deletable + * @property Model $deletable */ class Deletion extends Model implements Loggable { @@ -22,7 +22,7 @@ class Deletion extends Model implements Loggable } /** - * The the user that performed the deletion. + * Get the user that performed the deletion. */ public function deleter(): BelongsTo { @@ -48,7 +48,11 @@ class Deletion extends Model implements Loggable { $deletable = $this->deletable()->first(); - return "Deletion ({$this->id}) for {$deletable->getType()} ({$deletable->id}) {$deletable->name}"; + if ($deletable instanceof Entity) { + return "Deletion ({$this->id}) for {$deletable->getType()} ({$deletable->id}) {$deletable->name}"; + } + + return "Deletion ({$this->id})"; } /** diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index fbe0db41b..27d5dc6a4 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -106,7 +106,7 @@ class Page extends BookChild /** * Get the url of this page. */ - public function getUrl($path = ''): string + public function getUrl(string $path = ''): string { $parts = [ 'books', diff --git a/app/Entities/Models/PageRevision.php b/app/Entities/Models/PageRevision.php index b994e7a04..336fd67da 100644 --- a/app/Entities/Models/PageRevision.php +++ b/app/Entities/Models/PageRevision.php @@ -22,6 +22,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $html * @property int $revision_number * @property Page $page + * + * @property-read ?User $createdBy */ class PageRevision extends Model { diff --git a/app/Entities/Tools/PageEditActivity.php b/app/Entities/Tools/PageEditActivity.php index ef6c085ac..9981a6ed7 100644 --- a/app/Entities/Tools/PageEditActivity.php +++ b/app/Entities/Tools/PageEditActivity.php @@ -35,7 +35,13 @@ class PageEditActivity $pageDraftEdits = $this->activePageEditingQuery(60)->get(); $count = $pageDraftEdits->count(); - $userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]) : trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]); + $userMessage = trans('entities.pages_draft_edit_active.start_a', ['count' => $count]); + if ($count === 1) { + /** @var PageRevision $firstDraft */ + $firstDraft = $pageDraftEdits->first(); + $userMessage = trans('entities.pages_draft_edit_active.start_b', ['userName' => $firstDraft->createdBy->name ?? '']); + } + $timeMessage = trans('entities.pages_draft_edit_active.time_b', ['minCount'=> 60]); return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]); diff --git a/app/Entities/Tools/SearchRunner.php b/app/Entities/Tools/SearchRunner.php index df566eb0b..223494d46 100644 --- a/app/Entities/Tools/SearchRunner.php +++ b/app/Entities/Tools/SearchRunner.php @@ -47,7 +47,7 @@ class SearchRunner /** * Search all entities in the system. * The provided count is for each entity to search, - * Total returned could can be larger and not guaranteed. + * Total returned could be larger and not guaranteed. */ public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20, string $action = 'view'): array { @@ -68,11 +68,15 @@ class SearchRunner if (!in_array($entityType, $entityTypes)) { continue; } + $search = $this->searchEntityTable($searchOpts, $entityType, $page, $count, $action); + /** @var int $entityTotal */ $entityTotal = $this->searchEntityTable($searchOpts, $entityType, $page, $count, $action, true); - if ($entityTotal > $page * $count) { + + if ($entityTotal > ($page * $count)) { $hasMore = true; } + $total += $entityTotal; $results = $results->merge($search); } diff --git a/app/Entities/Tools/SiblingFetcher.php b/app/Entities/Tools/SiblingFetcher.php index e9dad0e13..249e0038e 100644 --- a/app/Entities/Tools/SiblingFetcher.php +++ b/app/Entities/Tools/SiblingFetcher.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\EntityProvider; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Models\Page; use Illuminate\Support\Collection; class SiblingFetcher @@ -18,18 +19,18 @@ class SiblingFetcher $entities = []; // Page in chapter - if ($entity->isA('page') && $entity->chapter) { + if ($entity instanceof Page && $entity->chapter) { $entities = $entity->chapter->getVisiblePages(); } // Page in book or chapter - if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) { + if (($entity instanceof Page && !$entity->chapter) || $entity->isA('chapter')) { $entities = $entity->book->getDirectChildren(); } // Book // Gets just the books in a shelf if shelf is in context - if ($entity->isA('book')) { + if ($entity instanceof Book) { $contextShelf = (new ShelfContext())->getContextualShelfForBook($entity); if ($contextShelf) { $entities = $contextShelf->visibleBooks()->get(); @@ -38,8 +39,8 @@ class SiblingFetcher } } - // Shelve - if ($entity->isA('bookshelf')) { + // Shelf + if ($entity instanceof Bookshelf) { $entities = Bookshelf::visible()->get(); } diff --git a/app/Entities/Tools/SlugGenerator.php b/app/Entities/Tools/SlugGenerator.php index 52e5700da..e715f769f 100644 --- a/app/Entities/Tools/SlugGenerator.php +++ b/app/Entities/Tools/SlugGenerator.php @@ -4,13 +4,14 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\BookChild; use BookStack\Interfaces\Sluggable; +use BookStack\Model; use Illuminate\Support\Str; class SlugGenerator { /** * Generate a fresh slug for the given entity. - * The slug will generated so it does not conflict within the same parent item. + * The slug will be generated so that it doesn't conflict within the same parent item. */ public function generate(Sluggable $model): string { @@ -38,6 +39,7 @@ class SlugGenerator /** * Check if a slug is already in-use for this * type of model within the same parent. + * @param Sluggable&Model $model */ protected function slugInUse(string $slug, Sluggable $model): bool { diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php index c5466aecd..3e7d4a836 100644 --- a/app/Http/Controllers/Auth/ConfirmEmailController.php +++ b/app/Http/Controllers/Auth/ConfirmEmailController.php @@ -10,10 +10,7 @@ use BookStack\Exceptions\UserTokenExpiredException; use BookStack\Exceptions\UserTokenNotFoundException; use BookStack\Http\Controllers\Controller; use Exception; -use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\Routing\Redirector; -use Illuminate\View\View; class ConfirmEmailController extends Controller { @@ -57,33 +54,23 @@ class ConfirmEmailController extends Controller /** * Confirms an email via a token and logs the user into the system. * - * @param $token - * * @throws ConfirmationEmailException * @throws Exception - * - * @return RedirectResponse|Redirector */ - public function confirm($token) + public function confirm(string $token) { try { $userId = $this->emailConfirmationService->checkTokenAndGetUserId($token); - } catch (Exception $exception) { - if ($exception instanceof UserTokenNotFoundException) { - $this->showErrorNotification(trans('errors.email_confirmation_invalid')); + } catch (UserTokenNotFoundException $exception) { + $this->showErrorNotification(trans('errors.email_confirmation_invalid')); - return redirect('/register'); - } + return redirect('/register'); + } catch (UserTokenExpiredException $exception) { + $user = $this->userRepo->getById($exception->userId); + $this->emailConfirmationService->sendConfirmation($user); + $this->showErrorNotification(trans('errors.email_confirmation_expired')); - if ($exception instanceof UserTokenExpiredException) { - $user = $this->userRepo->getById($exception->userId); - $this->emailConfirmationService->sendConfirmation($user); - $this->showErrorNotification(trans('errors.email_confirmation_expired')); - - return redirect('/register/confirm'); - } - - throw $exception; + return redirect('/register/confirm'); } $user = $this->userRepo->getById($userId); @@ -99,10 +86,6 @@ class ConfirmEmailController extends Controller /** * Resend the confirmation email. - * - * @param Request $request - * - * @return View */ public function resend(Request $request) { diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index df450d051..047e0bed9 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -165,7 +165,7 @@ abstract class Controller extends BaseController /** * Log an activity in the system. * - * @param string|Loggable + * @param $detail string|Loggable */ protected function logActivity(string $type, $detail = ''): void { diff --git a/app/Http/Controllers/FavouriteController.php b/app/Http/Controllers/FavouriteController.php index c8ba3078c..8643073c9 100644 --- a/app/Http/Controllers/FavouriteController.php +++ b/app/Http/Controllers/FavouriteController.php @@ -66,7 +66,7 @@ class FavouriteController extends Controller * @throws \Illuminate\Validation\ValidationException * @throws \Exception */ - protected function getValidatedModelFromRequest(Request $request): Favouritable + protected function getValidatedModelFromRequest(Request $request): Entity { $modelInfo = $this->validate($request, [ 'type' => ['required', 'string'], diff --git a/app/Http/Middleware/CheckUserHasPermission.php b/app/Http/Middleware/CheckUserHasPermission.php index 4a6a06468..b5678e734 100644 --- a/app/Http/Middleware/CheckUserHasPermission.php +++ b/app/Http/Middleware/CheckUserHasPermission.php @@ -12,7 +12,7 @@ class CheckUserHasPermission * * @param \Illuminate\Http\Request $request * @param \Closure $next - * @param $permission + * @param string $permission * * @return mixed */ diff --git a/app/Interfaces/Sluggable.php b/app/Interfaces/Sluggable.php index 24ee1bab2..2d56e847e 100644 --- a/app/Interfaces/Sluggable.php +++ b/app/Interfaces/Sluggable.php @@ -2,18 +2,12 @@ namespace BookStack\Interfaces; -use Illuminate\Database\Eloquent\Builder; - /** - * Interface Sluggable. - * * Assigned to models that can have slugs. * Must have the below properties. * * @property int $id * @property string $name - * - * @method Builder newQuery */ interface Sluggable { diff --git a/app/Model.php b/app/Model.php index 8520060f4..bd524332c 100644 --- a/app/Model.php +++ b/app/Model.php @@ -9,12 +9,9 @@ class Model extends EloquentModel /** * Provides public access to get the raw attribute value from the model. * Used in areas where no mutations are required but performance is critical. - * - * @param $key - * * @return mixed */ - public function getRawAttribute($key) + public function getRawAttribute(string $key) { return parent::getAttributeFromArray($key); } diff --git a/app/Traits/HasCreatorAndUpdater.php b/app/Traits/HasCreatorAndUpdater.php index a48936bc8..7c60be750 100644 --- a/app/Traits/HasCreatorAndUpdater.php +++ b/app/Traits/HasCreatorAndUpdater.php @@ -6,8 +6,8 @@ use BookStack\Auth\User; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** - * @property int created_by - * @property int updated_by + * @property int $created_by + * @property int $updated_by */ trait HasCreatorAndUpdater { diff --git a/app/Traits/HasOwner.php b/app/Traits/HasOwner.php index 6700ff4df..c0fefafa9 100644 --- a/app/Traits/HasOwner.php +++ b/app/Traits/HasOwner.php @@ -6,7 +6,7 @@ use BookStack\Auth\User; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** - * @property int owned_by + * @property int $owned_by */ trait HasOwner { diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index 2a4ea424f..494ff3ac0 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -236,7 +236,7 @@ class ImageRepo ->get(['id', 'name', 'slug', 'book_id']); foreach ($pages as $page) { - $page->url = $page->getUrl(); + $page->setAttribute('url', $page->getUrl()); } return $pages->all(); diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index 6d4902589..b8477eb40 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -8,6 +8,7 @@ use Exception; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\Filesystem as Storage; +use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Filesystem\FilesystemManager; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -436,10 +437,12 @@ class ImageService */ public function pathExistsInLocalSecure(string $imagePath): bool { + /** @var FilesystemAdapter $disk */ $disk = $this->getStorageDisk('gallery'); // Check local_secure is active return $this->usingSecureImages() + && $disk instanceof FilesystemAdapter // Check the image file exists && $disk->exists($imagePath) // Check the file is likely an image file diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 1943aa780..08dde7048 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -4,6 +4,7 @@ namespace BookStack\Util; use DOMAttr; use DOMDocument; +use DOMElement; use DOMNodeList; use DOMXPath; @@ -92,7 +93,9 @@ class HtmlContentFilter /** @var DOMAttr $attr */ foreach ($attrs as $attr) { $attrName = $attr->nodeName; - $attr->parentNode->removeAttribute($attrName); + /** @var DOMElement $parentNode */ + $parentNode = $attr->parentNode; + $parentNode->removeAttribute($attrName); } } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 815b1c187..f3aa47e12 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,7 +15,7 @@ parameters: - bootstrap/phpstan.php ignoreErrors: -# - '#Unsafe usage of new static#' + # - '#PHPDoc tag @throws with type .*?Psr\\SimpleCache\\InvalidArgumentException.*? is not subtype of Throwable#' excludePaths: - ./Config/**/*.php