1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-10-29 16:09:29 +03:00

Maintenance: Reached PHPstan level 2

Reworked some stuff around slugs to use interface in a better way.
Also standardised phpdoc to use @return instead of @returns
This commit is contained in:
Dan Brown
2025-09-02 16:02:52 +01:00
parent 1e34954554
commit cee23de6c5
34 changed files with 118 additions and 89 deletions

View File

@@ -51,7 +51,7 @@ class Saml2Service
* Returns the SAML2 request ID, and the URL to redirect the user to. * Returns the SAML2 request ID, and the URL to redirect the user to.
* *
* @throws Error * @throws Error
* @returns array{url: string, id: ?string} * @return array{url: string, id: ?string}
*/ */
public function logout(User $user): array public function logout(User $user): array
{ {

View File

@@ -55,7 +55,7 @@ class SocialDriverManager
/** /**
* Gets the names of the active social drivers, keyed by driver id. * Gets the names of the active social drivers, keyed by driver id.
* @returns array<string, string> * @return array<string, string>
*/ */
public function getActive(): array public function getActive(): array
{ {

View File

@@ -4,6 +4,8 @@ namespace BookStack\Activity\Models;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\HasCreatorAndUpdater;
use BookStack\Users\Models\OwnableInterface;
use BookStack\Users\Models\User;
use BookStack\Util\HtmlContentFilter; use BookStack\Util\HtmlContentFilter;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -17,12 +19,10 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
* @property int $local_id * @property int $local_id
* @property string $entity_type * @property string $entity_type
* @property int $entity_id * @property int $entity_id
* @property int $created_by
* @property int $updated_by
* @property string $content_ref * @property string $content_ref
* @property bool $archived * @property bool $archived
*/ */
class Comment extends Model implements Loggable class Comment extends Model implements Loggable, OwnableInterface
{ {
use HasFactory; use HasFactory;
use HasCreatorAndUpdater; use HasCreatorAndUpdater;
@@ -39,6 +39,7 @@ class Comment extends Model implements Loggable
/** /**
* Get the parent comment this is in reply to (if existing). * Get the parent comment this is in reply to (if existing).
* @return BelongsTo<Comment, Comment>
*/ */
public function parent(): BelongsTo public function parent(): BelongsTo
{ {

View File

@@ -36,7 +36,7 @@ class WatchLevels
/** /**
* Get all the possible values as an option_name => value array. * Get all the possible values as an option_name => value array.
* @returns array<string, int> * @return array<string, int>
*/ */
public static function all(): array public static function all(): array
{ {
@@ -50,7 +50,7 @@ class WatchLevels
/** /**
* Get the watch options suited for the given entity. * Get the watch options suited for the given entity.
* @returns array<string, int> * @return array<string, int>
*/ */
public static function allSuitedFor(Entity $entity): array public static function allSuitedFor(Entity $entity): array
{ {

View File

@@ -8,7 +8,7 @@ class Model extends EloquentModel
{ {
/** /**
* Provides public access to get the raw attribute value from the model. * Provides public access to get the raw attribute value from the model.
* Used in areas where no mutations are required but performance is critical. * Used in areas where no mutations are required, but performance is critical.
* *
* @return mixed * @return mixed
*/ */

View File

@@ -5,11 +5,8 @@ namespace BookStack\App;
/** /**
* Assigned to models that can have slugs. * Assigned to models that can have slugs.
* Must have the below properties. * Must have the below properties.
*
* @property int $id
* @property string $name
*/ */
interface Sluggable interface SluggableInterface
{ {
/** /**
* Regenerate the slug for this model. * Regenerate the slug for this model.

View File

@@ -6,10 +6,10 @@ use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\DeletionRepo; use BookStack\Entities\Repos\DeletionRepo;
use BookStack\Http\ApiController; use BookStack\Http\ApiController;
use Closure; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
class RecycleBinApiController extends ApiController class RecycleBinApiController extends ApiController
{ {
@@ -40,7 +40,7 @@ class RecycleBinApiController extends ApiController
'updated_at', 'updated_at',
'deletable_type', 'deletable_type',
'deletable_id', 'deletable_id',
], [Closure::fromCallable([$this, 'listFormatter'])]); ], [$this->listFormatter(...)]);
} }
/** /**
@@ -72,7 +72,6 @@ class RecycleBinApiController extends ApiController
protected function listFormatter(Deletion $deletion) protected function listFormatter(Deletion $deletion)
{ {
$deletable = $deletion->deletable; $deletable = $deletion->deletable;
$withTrashedQuery = fn (Builder $query) => $query->withTrashed();
if ($deletable instanceof BookChild) { if ($deletable instanceof BookChild) {
$parent = $deletable->getParent(); $parent = $deletable->getParent();
@@ -81,11 +80,19 @@ class RecycleBinApiController extends ApiController
} }
if ($deletable instanceof Book || $deletable instanceof Chapter) { if ($deletable instanceof Book || $deletable instanceof Chapter) {
$countsToLoad = ['pages' => $withTrashedQuery]; $countsToLoad = ['pages' => static::withTrashedQuery(...)];
if ($deletable instanceof Book) { if ($deletable instanceof Book) {
$countsToLoad['chapters'] = $withTrashedQuery; $countsToLoad['chapters'] = static::withTrashedQuery(...);
} }
$deletable->loadCount($countsToLoad); $deletable->loadCount($countsToLoad);
} }
} }
/**
* @param HasMany<Chapter|Page, Book|Chapter> $query
*/
protected static function withTrashedQuery(HasMany $query): void
{
$query->withTrashed();
}
} }

View File

@@ -95,6 +95,7 @@ class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterfa
/** /**
* Get all pages within this book. * Get all pages within this book.
* @return HasMany<Page, $this>
*/ */
public function pages(): HasMany public function pages(): HasMany
{ {
@@ -111,7 +112,7 @@ class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterfa
/** /**
* Get all chapters within this book. * Get all chapters within this book.
* @return HasMany<Chapter> * @return HasMany<Chapter, $this>
*/ */
public function chapters(): HasMany public function chapters(): HasMany
{ {

View File

@@ -70,6 +70,7 @@ class Bookshelf extends Entity implements CoverImageInterface, HtmlDescriptionIn
/** /**
* Get the cover image of the shelf. * Get the cover image of the shelf.
* @return BelongsTo<Image, $this>
*/ */
public function cover(): BelongsTo public function cover(): BelongsTo
{ {

View File

@@ -27,7 +27,7 @@ class Chapter extends BookChild implements HtmlDescriptionInterface
/** /**
* Get the pages that this chapter contains. * Get the pages that this chapter contains.
* *
* @return HasMany<Page> * @return HasMany<Page, $this>
*/ */
public function pages(string $dir = 'ASC'): HasMany public function pages(string $dir = 'ASC'): HasMany
{ {
@@ -60,7 +60,7 @@ class Chapter extends BookChild implements HtmlDescriptionInterface
/** /**
* Get the visible pages in this chapter. * Get the visible pages in this chapter.
* @returns Collection<Page> * @return Collection<Page>
*/ */
public function getVisiblePages(): Collection public function getVisiblePages(): Collection
{ {

View File

@@ -12,7 +12,7 @@ use BookStack\Activity\Models\View;
use BookStack\Activity\Models\Viewable; use BookStack\Activity\Models\Viewable;
use BookStack\Activity\Models\Watch; use BookStack\Activity\Models\Watch;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\App\Sluggable; use BookStack\App\SluggableInterface;
use BookStack\Entities\Tools\SlugGenerator; use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Permissions\JointPermissionBuilder; use BookStack\Permissions\JointPermissionBuilder;
use BookStack\Permissions\Models\EntityPermission; use BookStack\Permissions\Models\EntityPermission;
@@ -22,7 +22,8 @@ use BookStack\References\Reference;
use BookStack\Search\SearchIndex; use BookStack\Search\SearchIndex;
use BookStack\Search\SearchTerm; use BookStack\Search\SearchTerm;
use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\HasCreatorAndUpdater;
use BookStack\Users\Models\HasOwner; use BookStack\Users\Models\OwnableInterface;
use BookStack\Users\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@@ -43,17 +44,23 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property Carbon $deleted_at * @property Carbon $deleted_at
* @property int $created_by * @property int $created_by
* @property int $updated_by * @property int $updated_by
* @property int $owned_by
* @property Collection $tags * @property Collection $tags
* *
* @method static Entity|Builder visible() * @method static Entity|Builder visible()
* @method static Builder withLastView() * @method static Builder withLastView()
* @method static Builder withViewCount() * @method static Builder withViewCount()
*/ */
abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, DeletableInterface, Loggable abstract class Entity extends Model implements
SluggableInterface,
Favouritable,
Viewable,
DeletableInterface,
OwnableInterface,
Loggable
{ {
use SoftDeletes; use SoftDeletes;
use HasCreatorAndUpdater; use HasCreatorAndUpdater;
use HasOwner;
/** /**
* @var string - Name of property where the main text content is found * @var string - Name of property where the main text content is found
@@ -200,6 +207,20 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
return $this->morphMany(JointPermission::class, 'entity'); return $this->morphMany(JointPermission::class, 'entity');
} }
/**
* Get the user who owns this entity.
* @return BelongsTo<User, $this>
*/
public function ownedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'owned_by');
}
public function getOwnerFieldName(): string
{
return 'owned_by';
}
/** /**
* Get the related delete records for this entity. * Get the related delete records for this entity.
*/ */
@@ -318,7 +339,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/ */
public function refreshSlug(): string public function refreshSlug(): string
{ {
$this->slug = app()->make(SlugGenerator::class)->generate($this); $this->slug = app()->make(SlugGenerator::class)->generate($this, $this->name);
return $this->slug; return $this->slug;
} }

View File

@@ -13,6 +13,9 @@ class BookQueries implements ProvidesEntityQueries
'created_at', 'updated_at', 'image_id', 'owned_by', 'created_at', 'updated_at', 'image_id', 'owned_by',
]; ];
/**
* @return Builder<Book>
*/
public function start(): Builder public function start(): Builder
{ {
return Book::query(); return Book::query();

View File

@@ -13,6 +13,9 @@ class BookshelfQueries implements ProvidesEntityQueries
'created_at', 'updated_at', 'image_id', 'owned_by', 'created_at', 'updated_at', 'image_id', 'owned_by',
]; ];
/**
* @return Builder<Bookshelf>
*/
public function start(): Builder public function start(): Builder
{ {
return Bookshelf::query(); return Bookshelf::query();

View File

@@ -35,6 +35,7 @@ class EntityQueries
/** /**
* Start a query of visible entities of the given type, * Start a query of visible entities of the given type,
* suitable for listing display. * suitable for listing display.
* @return Builder<Entity>
*/ */
public function visibleForList(string $entityType): Builder public function visibleForList(string $entityType): Builder
{ {
@@ -44,7 +45,6 @@ class EntityQueries
protected function getQueriesForType(string $type): ProvidesEntityQueries protected function getQueriesForType(string $type): ProvidesEntityQueries
{ {
/** @var ?ProvidesEntityQueries $queries */
$queries = match ($type) { $queries = match ($type) {
'page' => $this->pages, 'page' => $this->pages,
'chapter' => $this->chapters, 'chapter' => $this->chapters,

View File

@@ -22,13 +22,14 @@ interface ProvidesEntityQueries
public function start(): Builder; public function start(): Builder;
/** /**
* Find the entity of the given ID, or return null if not found. * Find the entity of the given ID or return null if not found.
*/ */
public function findVisibleById(int $id): ?Entity; public function findVisibleById(int $id): ?Entity;
/** /**
* Start a query for items that are visible, with selection * Start a query for items that are visible, with selection
* configured for list display of this item. * configured for list display of this item.
* @return Builder<Entity>
*/ */
public function visibleForList(): Builder; public function visibleForList(): Builder;
} }

View File

@@ -89,12 +89,10 @@ class BaseRepo
/** /**
* Update the given items' cover image, or clear it. * Update the given items' cover image, or clear it.
* *
* @param Entity&CoverImageInterface $entity
*
* @throws ImageUploadException * @throws ImageUploadException
* @throws \Exception * @throws \Exception
*/ */
public function updateCoverImage($entity, ?UploadedFile $coverImage, bool $removeImage = false) public function updateCoverImage(Entity&CoverImageInterface $entity, ?UploadedFile $coverImage, bool $removeImage = false)
{ {
if ($coverImage) { if ($coverImage) {
$imageType = $entity->coverImageTypeKey(); $imageType = $entity->coverImageTypeKey();
@@ -106,7 +104,7 @@ class BaseRepo
if ($removeImage) { if ($removeImage) {
$this->imageRepo->destroyImage($entity->cover()->first()); $this->imageRepo->destroyImage($entity->cover()->first());
$entity->image_id = 0; $entity->cover()->dissociate();
$entity->save(); $entity->save();
} }
} }

View File

@@ -3,7 +3,7 @@
namespace BookStack\Entities\Tools; namespace BookStack\Entities\Tools;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\App\Sluggable; use BookStack\App\SluggableInterface;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -13,9 +13,9 @@ class SlugGenerator
* Generate a fresh slug for the given entity. * Generate a fresh slug for the given entity.
* The slug will be generated so that it doesn't 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 public function generate(SluggableInterface&Model $model, string $slugSource): string
{ {
$slug = $this->formatNameAsSlug($model->name); $slug = $this->formatNameAsSlug($slugSource);
while ($this->slugInUse($slug, $model)) { while ($this->slugInUse($slug, $model)) {
$slug .= '-' . Str::random(3); $slug .= '-' . Str::random(3);
} }
@@ -24,7 +24,7 @@ class SlugGenerator
} }
/** /**
* Format a name as a url slug. * Format a name as a URL slug.
*/ */
protected function formatNameAsSlug(string $name): string protected function formatNameAsSlug(string $name): string
{ {
@@ -39,10 +39,8 @@ class SlugGenerator
/** /**
* Check if a slug is already in-use for this * Check if a slug is already in-use for this
* type of model within the same parent. * type of model within the same parent.
*
* @param Sluggable&Model $model
*/ */
protected function slugInUse(string $slug, Sluggable $model): bool protected function slugInUse(string $slug, SluggableInterface&Model $model): bool
{ {
$query = $model->newQuery()->where('slug', '=', $slug); $query = $model->newQuery()->where('slug', '=', $slug);

View File

@@ -7,8 +7,7 @@ use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Permissions\Models\EntityPermission; use BookStack\Permissions\Models\EntityPermission;
use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\OwnableInterface;
use BookStack\Users\Models\HasOwner;
use BookStack\Users\Models\User; use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Query\Builder as QueryBuilder;
@@ -24,10 +23,8 @@ class PermissionApplicator
/** /**
* Checks if an entity has a restriction set upon it. * Checks if an entity has a restriction set upon it.
*
* @param Model&(HasCreatorAndUpdater|HasOwner) $ownable
*/ */
public function checkOwnableUserAccess(Model $ownable, string $permission): bool public function checkOwnableUserAccess(Model&OwnableInterface $ownable, string $permission): bool
{ {
$explodedPermission = explode('-', $permission); $explodedPermission = explode('-', $permission);
$action = $explodedPermission[1] ?? $explodedPermission[0]; $action = $explodedPermission[1] ?? $explodedPermission[0];
@@ -39,7 +36,7 @@ class PermissionApplicator
$allRolePermission = $user->can($fullPermission . '-all'); $allRolePermission = $user->can($fullPermission . '-all');
$ownRolePermission = $user->can($fullPermission . '-own'); $ownRolePermission = $user->can($fullPermission . '-own');
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment']; $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by'; $ownerField = $ownable->getOwnerFieldName();
$ownableFieldVal = $ownable->getAttribute($ownerField); $ownableFieldVal = $ownable->getAttribute($ownerField);
if (is_null($ownableFieldVal)) { if (is_null($ownableFieldVal)) {
@@ -49,11 +46,15 @@ class PermissionApplicator
$isOwner = $user->id === $ownableFieldVal; $isOwner = $user->id === $ownableFieldVal;
$hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission); $hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission);
// Handle non entity specific jointPermissions // Handle non-entity-specific jointPermissions
if (in_array($explodedPermission[0], $nonJointPermissions)) { if (in_array($explodedPermission[0], $nonJointPermissions)) {
return $hasRolePermission; return $hasRolePermission;
} }
if (!($ownable instanceof Entity)) {
return false;
}
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action); $hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions; return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;

View File

@@ -48,7 +48,7 @@ class CrossLinkParser
/** /**
* Get a list of href values from the given document. * Get a list of href values from the given document.
* *
* @returns string[] * @return string[]
*/ */
protected function getLinksFromContent(string $html): array protected function getLinksFromContent(string $html): array
{ {

View File

@@ -119,7 +119,7 @@ class SearchIndex
* Create a scored term array from the given text, where the keys are the terms * Create a scored term array from the given text, where the keys are the terms
* and the values are their scores. * and the values are their scores.
* *
* @returns array<string, int> * @return array<string, int>
*/ */
protected function generateTermScoreMapFromText(string $text, float $scoreAdjustment = 1): array protected function generateTermScoreMapFromText(string $text, float $scoreAdjustment = 1): array
{ {
@@ -136,7 +136,7 @@ class SearchIndex
* Create a scored term array from the given HTML, where the keys are the terms * Create a scored term array from the given HTML, where the keys are the terms
* and the values are their scores. * and the values are their scores.
* *
* @returns array<string, int> * @return array<string, int>
*/ */
protected function generateTermScoreMapFromHtml(string $html): array protected function generateTermScoreMapFromHtml(string $html): array
{ {
@@ -177,7 +177,7 @@ class SearchIndex
* *
* @param Tag[] $tags * @param Tag[] $tags
* *
* @returns array<string, int> * @return array<string, int>
*/ */
protected function generateTermScoreMapFromTags(array $tags): array protected function generateTermScoreMapFromTags(array $tags): array
{ {
@@ -199,7 +199,7 @@ class SearchIndex
* For the given text, return an array where the keys are the unique term words * For the given text, return an array where the keys are the unique term words
* and the values are the frequency of that term. * and the values are the frequency of that term.
* *
* @returns array<string, int> * @return array<string, int>
*/ */
protected function textToTermCountMap(string $text): array protected function textToTermCountMap(string $text): array
{ {
@@ -243,7 +243,7 @@ class SearchIndex
* For the given entity, Generate an array of term data details. * For the given entity, Generate an array of term data details.
* Is the raw term data, not instances of SearchTerm models. * Is the raw term data, not instances of SearchTerm models.
* *
* @returns array{term: string, score: float, entity_id: int, entity_type: string}[] * @return array{term: string, score: float, entity_id: int, entity_type: string}[]
*/ */
protected function entityToTermDataArray(Entity $entity): array protected function entityToTermDataArray(Entity $entity): array
{ {
@@ -279,7 +279,7 @@ class SearchIndex
* *
* @param array<string, int>[] ...$scoreMaps * @param array<string, int>[] ...$scoreMaps
* *
* @returns array<string, int> * @return array<string, int>
*/ */
protected function mergeTermScoreMaps(...$scoreMaps): array protected function mergeTermScoreMaps(...$scoreMaps): array
{ {

View File

@@ -78,7 +78,7 @@ class BookSorter
* Sort the books content using the given sort map. * Sort the books content using the given sort map.
* Returns a list of books that were involved in the operation. * Returns a list of books that were involved in the operation.
* *
* @returns Book[] * @return Book[]
*/ */
public function sortUsingMap(BookSortMap $sortMap): array public function sortUsingMap(BookSortMap $sortMap): array
{ {

View File

@@ -62,7 +62,7 @@ class ThemeEvents
* *
* @param string $authSystem * @param string $authSystem
* @param array $userData * @param array $userData
* @returns bool|null * @return bool|null
*/ */
const AUTH_PRE_REGISTER = 'auth_pre_register'; const AUTH_PRE_REGISTER = 'auth_pre_register';
@@ -83,7 +83,7 @@ class ThemeEvents
* If the listener returns a non-null value, that will be used as an environment instead. * If the listener returns a non-null value, that will be used as an environment instead.
* *
* @param \League\CommonMark\Environment\Environment $environment * @param \League\CommonMark\Environment\Environment $environment
* @returns \League\CommonMark\Environment\Environment|null * @return \League\CommonMark\Environment\Environment|null
*/ */
const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure'; const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
@@ -96,7 +96,7 @@ class ThemeEvents
* *
* @param array $idTokenData * @param array $idTokenData
* @param array $accessTokenData * @param array $accessTokenData
* @returns array|null * @return array|null
*/ */
const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate'; const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate';
@@ -142,7 +142,7 @@ class ThemeEvents
* Return values, if provided, will be used as a new response to use. * Return values, if provided, will be used as a new response to use.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @returns \Illuminate\Http\Response|null * @return \Illuminate\Http\Response|null
*/ */
const WEB_MIDDLEWARE_BEFORE = 'web_middleware_before'; const WEB_MIDDLEWARE_BEFORE = 'web_middleware_before';
@@ -154,7 +154,7 @@ class ThemeEvents
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse $response * @param \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse $response
* @returns \Illuminate\Http\Response|null * @return \Illuminate\Http\Response|null
*/ */
const WEB_MIDDLEWARE_AFTER = 'web_middleware_after'; const WEB_MIDDLEWARE_AFTER = 'web_middleware_after';
@@ -173,7 +173,7 @@ class ThemeEvents
* @param string|\BookStack\Activity\Models\Loggable $detail * @param string|\BookStack\Activity\Models\Loggable $detail
* @param \BookStack\Users\Models\User $initiator * @param \BookStack\Users\Models\User $initiator
* @param int $initiatedTime * @param int $initiatedTime
* @returns array|null * @return array|null
*/ */
const WEBHOOK_CALL_BEFORE = 'webhook_call_before'; const WEBHOOK_CALL_BEFORE = 'webhook_call_before';
} }

View File

@@ -8,6 +8,7 @@ use BookStack\Entities\Models\Page;
use BookStack\Permissions\Models\JointPermission; use BookStack\Permissions\Models\JointPermission;
use BookStack\Permissions\PermissionApplicator; use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\HasCreatorAndUpdater;
use BookStack\Users\Models\OwnableInterface;
use BookStack\Users\Models\User; use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -27,7 +28,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
* *
* @method static Entity|Builder visible() * @method static Entity|Builder visible()
*/ */
class Attachment extends Model class Attachment extends Model implements OwnableInterface
{ {
use HasCreatorAndUpdater; use HasCreatorAndUpdater;
use HasFactory; use HasFactory;

View File

@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Page;
use BookStack\Permissions\Models\JointPermission; use BookStack\Permissions\Models\JointPermission;
use BookStack\Permissions\PermissionApplicator; use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\HasCreatorAndUpdater; use BookStack\Users\Models\HasCreatorAndUpdater;
use BookStack\Users\Models\OwnableInterface;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -21,7 +22,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
* @property int $created_by * @property int $created_by
* @property int $updated_by * @property int $updated_by
*/ */
class Image extends Model class Image extends Model implements OwnableInterface
{ {
use HasFactory; use HasFactory;
use HasCreatorAndUpdater; use HasCreatorAndUpdater;

View File

@@ -138,7 +138,7 @@ class ImageService
* Get the raw data content from an image. * Get the raw data content from an image.
* *
* @throws Exception * @throws Exception
* @returns ?resource * @return ?resource
*/ */
public function getImageStream(Image $image): mixed public function getImageStream(Image $image): mixed
{ {

View File

@@ -61,7 +61,7 @@ class ImageStorageDisk
/** /**
* Get a stream to the file at the given path. * Get a stream to the file at the given path.
* @returns ?resource * @return ?resource
*/ */
public function stream(string $path): mixed public function stream(string $path): mixed
{ {

View File

@@ -81,7 +81,7 @@ class UserApiController extends ApiController
return $this->apiListingResponse($users, [ return $this->apiListingResponse($users, [
'id', 'name', 'slug', 'email', 'external_auth_id', 'id', 'name', 'slug', 'email', 'external_auth_id',
'created_at', 'updated_at', 'last_activity_at', 'created_at', 'updated_at', 'last_activity_at',
], [Closure::fromCallable([$this, 'listFormatter'])]); ], [$this->listFormatter(...)]);
} }
/** /**

View File

@@ -27,4 +27,9 @@ trait HasCreatorAndUpdater
{ {
return $this->belongsTo(User::class, 'updated_by'); return $this->belongsTo(User::class, 'updated_by');
} }
public function getOwnerFieldName(): string
{
return 'created_by';
}
} }

View File

@@ -1,19 +0,0 @@
<?php
namespace BookStack\Users\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $owned_by
*/
trait HasOwner
{
/**
* Relation for the user that owns this entity.
*/
public function ownedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'owned_by');
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace BookStack\Users\Models;
interface OwnableInterface
{
public function getOwnerFieldName(): string;
}

View File

@@ -53,6 +53,7 @@ class Role extends Model implements Loggable
/** /**
* The RolePermissions that belong to the role. * The RolePermissions that belong to the role.
* @return BelongsToMany<RolePermission, $this>
*/ */
public function permissions(): BelongsToMany public function permissions(): BelongsToMany
{ {

View File

@@ -10,7 +10,7 @@ use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Watch; use BookStack\Activity\Models\Watch;
use BookStack\Api\ApiToken; use BookStack\Api\ApiToken;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\App\Sluggable; use BookStack\App\SluggableInterface;
use BookStack\Entities\Tools\SlugGenerator; use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Translation\LocaleDefinition; use BookStack\Translation\LocaleDefinition;
use BookStack\Translation\LocaleManager; use BookStack\Translation\LocaleManager;
@@ -47,7 +47,7 @@ use Illuminate\Support\Collection;
* @property Collection $mfaValues * @property Collection $mfaValues
* @property ?Image $avatar * @property ?Image $avatar
*/ */
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, SluggableInterface
{ {
use HasFactory; use HasFactory;
use Authenticatable; use Authenticatable;
@@ -372,7 +372,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/ */
public function refreshSlug(): string public function refreshSlug(): string
{ {
$this->slug = app()->make(SlugGenerator::class)->generate($this); $this->slug = app()->make(SlugGenerator::class)->generate($this, $this->name);
return $this->slug; return $this->slug;
} }

View File

@@ -14,7 +14,7 @@
<arg name="parallel" value="75"/> <arg name="parallel" value="75"/>
<arg value="np"/> <arg value="np"/>
<rule ref="PSR12"/> <rule ref="PSR12"/>
<rule ref="PSR1.Methods.CamelCapsMethodName"> <rule ref="PSR1.Methods.CamelCapsMethodName">
<exclude-pattern>./tests/*</exclude-pattern> <exclude-pattern>./tests/*</exclude-pattern>

View File

@@ -7,7 +7,7 @@ parameters:
- app - app
# The level 8 is the highest level # The level 8 is the highest level
level: 1 level: 2
phpVersion: phpVersion:
min: 80200 min: 80200