*/
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;
}
}
}
// Attach avatar if non-existent
- if (is_null($user->avatar)) {
+ if (!$user->avatar()->exists()) {
$this->ldapService->saveAndAttachAvatar($user, $userDetails);
}
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);
}
* 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);
}
/**
* 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);
}
return 1;
}
- /** @var User $user */
$field = $id ? 'id' : 'email';
$value = $id ?: $email;
+
+ /** @var User $user */
$user = User::query()
->where($field, '=', $value)
->first();
namespace BookStack\Entities\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
/**
* Class Chapter.
*
* @property Collection<Page> $pages
- * @property mixed description
+ * @property string $description
*/
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);
}
/**
* Get the url of this chapter.
*/
- public function getUrl($path = ''): string
+ public function getUrl(string $path = ''): string
{
$parts = [
'books',
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
- * @property Model deletable
+ * @property Model $deletable
*/
class Deletion extends Model implements Loggable
{
}
/**
- * The the user that performed the deletion.
+ * Get the user that performed the deletion.
*/
public function deleter(): BelongsTo
{
{
$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})";
}
/**
/**
* Get the url of this page.
*/
- public function getUrl($path = ''): string
+ public function getUrl(string $path = ''): string
{
$parts = [
'books',
* @property string $html
* @property int $revision_number
* @property Page $page
+ *
+ * @property-read ?User $createdBy
*/
class PageRevision extends Model
{
$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]);
/**
* 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
{
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);
}
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
$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();
}
}
- // Shelve
- if ($entity->isA('bookshelf')) {
+ // Shelf
+ if ($entity instanceof Bookshelf) {
$entities = Bookshelf::visible()->get();
}
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
{
/**
* 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
{
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
{
/**
* 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'));
-
- return redirect('/register');
- }
-
- if ($exception instanceof UserTokenExpiredException) {
- $user = $this->userRepo->getById($exception->userId);
- $this->emailConfirmationService->sendConfirmation($user);
- $this->showErrorNotification(trans('errors.email_confirmation_expired'));
+ } catch (UserTokenNotFoundException $exception) {
+ $this->showErrorNotification(trans('errors.email_confirmation_invalid'));
- return redirect('/register/confirm');
- }
+ return redirect('/register');
+ } catch (UserTokenExpiredException $exception) {
+ $user = $this->userRepo->getById($exception->userId);
+ $this->emailConfirmationService->sendConfirmation($user);
+ $this->showErrorNotification(trans('errors.email_confirmation_expired'));
- throw $exception;
+ return redirect('/register/confirm');
}
$user = $this->userRepo->getById($userId);
/**
* Resend the confirmation email.
- *
- * @param Request $request
- *
- * @return View
*/
public function resend(Request $request)
{
/**
* Log an activity in the system.
*
- * @param string|Loggable
+ * @param $detail string|Loggable
*/
protected function logActivity(string $type, $detail = ''): void
{
* @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'],
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
- * @param $permission
+ * @param string $permission
*
* @return mixed
*/
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
{
/**
* 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);
}
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
- * @property int created_by
- * @property int updated_by
+ * @property int $created_by
+ * @property int $updated_by
*/
trait HasCreatorAndUpdater
{
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
- * @property int owned_by
+ * @property int $owned_by
*/
trait HasOwner
{
->get(['id', 'name', 'slug', 'book_id']);
foreach ($pages as $page) {
- $page->url = $page->getUrl();
+ $page->setAttribute('url', $page->getUrl());
}
return $pages->all();
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;
*/
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
use DOMAttr;
use DOMDocument;
+use DOMElement;
use DOMNodeList;
use DOMXPath;
/** @var DOMAttr $attr */
foreach ($attrs as $attr) {
$attrName = $attr->nodeName;
- $attr->parentNode->removeAttribute($attrName);
+ /** @var DOMElement $parentNode */
+ $parentNode = $attr->parentNode;
+ $parentNode->removeAttribute($attrName);
}
}
}
- 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