mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-09-09 06:29:32 +03:00
Merge pull request #5785 from BookStackApp/phpstan_level2
PHPstan level 3
This commit is contained in:
@@ -2,33 +2,18 @@
|
||||
|
||||
namespace BookStack\Access;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ExternalBaseUserProvider implements UserProvider
|
||||
{
|
||||
public function __construct(
|
||||
protected string $model
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the model.
|
||||
*/
|
||||
public function createModel(): Model
|
||||
{
|
||||
$class = '\\' . ltrim($this->model, '\\');
|
||||
|
||||
return new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a user by their unique identifier.
|
||||
*/
|
||||
public function retrieveById(mixed $identifier): ?Authenticatable
|
||||
{
|
||||
return $this->createModel()->newQuery()->find($identifier);
|
||||
return User::query()->find($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,10 +44,7 @@ class ExternalBaseUserProvider implements UserProvider
|
||||
*/
|
||||
public function retrieveByCredentials(array $credentials): ?Authenticatable
|
||||
{
|
||||
// Search current user base by looking up a uid
|
||||
$model = $this->createModel();
|
||||
|
||||
return $model->newQuery()
|
||||
return User::query()
|
||||
->where('external_auth_id', $credentials['external_auth_id'])
|
||||
->first();
|
||||
}
|
||||
|
@@ -3,23 +3,18 @@
|
||||
namespace BookStack\Access\Guards;
|
||||
|
||||
/**
|
||||
* Saml2 Session Guard.
|
||||
* External Auth Session Guard.
|
||||
*
|
||||
* The saml2 login process is async in nature meaning it does not fit very well
|
||||
* into the default laravel 'Guard' auth flow. Instead most of the logic is done
|
||||
* via the Saml2 controller & Saml2Service. This class provides a safer, thin
|
||||
* version of SessionGuard.
|
||||
* The login process for external auth (SAML2/OIDC) is async in nature, meaning it does not fit very well
|
||||
* into the default laravel 'Guard' auth flow. Instead, most of the logic is done via the relevant
|
||||
* controller and services. This class provides a safer, thin version of SessionGuard.
|
||||
*/
|
||||
class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard
|
||||
{
|
||||
/**
|
||||
* Validate a user's credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(array $credentials = [])
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -27,12 +22,9 @@ class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard
|
||||
/**
|
||||
* Attempt to authenticate a user using the given credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @param bool $remember
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function attempt(array $credentials = [], $remember = false)
|
||||
public function attempt(array $credentials = [], $remember = false): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ namespace BookStack\Access\Guards;
|
||||
|
||||
use BookStack\Access\RegistrationService;
|
||||
use Illuminate\Auth\GuardHelpers;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
@@ -24,43 +24,31 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
* The name of the Guard. Typically "session".
|
||||
*
|
||||
* Corresponds to guard name in authentication configuration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
protected readonly string $name;
|
||||
|
||||
/**
|
||||
* The user we last attempted to retrieve.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Auth\Authenticatable
|
||||
*/
|
||||
protected $lastAttempted;
|
||||
protected Authenticatable|null $lastAttempted;
|
||||
|
||||
/**
|
||||
* The session used by the guard.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Session\Session
|
||||
*/
|
||||
protected $session;
|
||||
protected Session $session;
|
||||
|
||||
/**
|
||||
* Indicates if the logout method has been called.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $loggedOut = false;
|
||||
protected bool $loggedOut = false;
|
||||
|
||||
/**
|
||||
* Service to handle common registration actions.
|
||||
*
|
||||
* @var RegistrationService
|
||||
*/
|
||||
protected $registrationService;
|
||||
protected RegistrationService $registrationService;
|
||||
|
||||
/**
|
||||
* Create a new authentication guard.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $name, UserProvider $provider, Session $session, RegistrationService $registrationService)
|
||||
{
|
||||
@@ -72,13 +60,11 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Get the currently authenticated user.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable|null
|
||||
*/
|
||||
public function user()
|
||||
public function user(): Authenticatable|null
|
||||
{
|
||||
if ($this->loggedOut) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we've already retrieved the user for the current request we can just
|
||||
@@ -101,13 +87,11 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Get the ID for the currently authenticated user.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function id()
|
||||
public function id(): int|null
|
||||
{
|
||||
if ($this->loggedOut) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->user()
|
||||
@@ -117,12 +101,8 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Log a user into the application without sessions or cookies.
|
||||
*
|
||||
* @param array $credentials
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function once(array $credentials = [])
|
||||
public function once(array $credentials = []): bool
|
||||
{
|
||||
if ($this->validate($credentials)) {
|
||||
$this->setUser($this->lastAttempted);
|
||||
@@ -135,12 +115,8 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Log the given user ID into the application without sessions or cookies.
|
||||
*
|
||||
* @param mixed $id
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable|false
|
||||
*/
|
||||
public function onceUsingId($id)
|
||||
public function onceUsingId($id): Authenticatable|false
|
||||
{
|
||||
if (!is_null($user = $this->provider->retrieveById($id))) {
|
||||
$this->setUser($user);
|
||||
@@ -153,38 +129,26 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Validate a user's credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(array $credentials = [])
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate a user using the given credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @param bool $remember
|
||||
*
|
||||
* @return bool
|
||||
* @param bool $remember
|
||||
*/
|
||||
public function attempt(array $credentials = [], $remember = false)
|
||||
public function attempt(array $credentials = [], $remember = false): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given user ID into the application.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param bool $remember
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable|false
|
||||
*/
|
||||
public function loginUsingId($id, $remember = false)
|
||||
public function loginUsingId(mixed $id, $remember = false): Authenticatable|false
|
||||
{
|
||||
// Always return false as to disable this method,
|
||||
// Logins should route through LoginService.
|
||||
@@ -194,12 +158,9 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
/**
|
||||
* Log a user into the application.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param bool $remember
|
||||
*
|
||||
* @return void
|
||||
* @param bool $remember
|
||||
*/
|
||||
public function login(AuthenticatableContract $user, $remember = false)
|
||||
public function login(Authenticatable $user, $remember = false): void
|
||||
{
|
||||
$this->updateSession($user->getAuthIdentifier());
|
||||
|
||||
@@ -208,12 +169,8 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Update the session with the given ID.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function updateSession($id)
|
||||
protected function updateSession(string|int $id): void
|
||||
{
|
||||
$this->session->put($this->getName(), $id);
|
||||
|
||||
@@ -222,10 +179,8 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
public function logout(): void
|
||||
{
|
||||
$this->clearUserDataFromStorage();
|
||||
|
||||
@@ -239,62 +194,48 @@ class ExternalBaseSessionGuard implements StatefulGuard
|
||||
|
||||
/**
|
||||
* Remove the user data from the session and cookies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearUserDataFromStorage()
|
||||
protected function clearUserDataFromStorage(): void
|
||||
{
|
||||
$this->session->remove($this->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last user we attempted to authenticate.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable
|
||||
*/
|
||||
public function getLastAttempted()
|
||||
public function getLastAttempted(): Authenticatable
|
||||
{
|
||||
return $this->lastAttempted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique identifier for the auth session value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return 'login_' . $this->name . '_' . sha1(static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user was authenticated via "remember me" cookie.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function viaRemember()
|
||||
public function viaRemember(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently cached user.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\Authenticatable|null
|
||||
*/
|
||||
public function getUser()
|
||||
public function getUser(): Authenticatable|null
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current user.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(AuthenticatableContract $user)
|
||||
public function setUser(Authenticatable $user): self
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
|
@@ -35,13 +35,9 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
|
||||
/**
|
||||
* Validate a user's credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
*
|
||||
* @throws LdapException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(array $credentials = [])
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
$userDetails = $this->ldapService->getUserDetails($credentials['username']);
|
||||
|
||||
|
@@ -95,7 +95,7 @@ class LoginService
|
||||
{
|
||||
$value = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
|
||||
if (!$value) {
|
||||
return ['user_id' => null, 'method' => null];
|
||||
return ['user_id' => null, 'method' => null, 'remember' => false];
|
||||
}
|
||||
|
||||
[$id, $method, $remember, $time] = explode(':', $value);
|
||||
@@ -103,18 +103,18 @@ class LoginService
|
||||
if ($time < $hourAgo) {
|
||||
$this->clearLastLoginAttempted();
|
||||
|
||||
return ['user_id' => null, 'method' => null];
|
||||
return ['user_id' => null, 'method' => null, 'remember' => false];
|
||||
}
|
||||
|
||||
return ['user_id' => $id, 'method' => $method, 'remember' => boolval($remember)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last login attempted user.
|
||||
* Set the last login-attempted user.
|
||||
* Must be only used when credentials are correct and a login could be
|
||||
* achieved but a secondary factor has stopped the login.
|
||||
* achieved, but a secondary factor has stopped the login.
|
||||
*/
|
||||
protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember)
|
||||
protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember): void
|
||||
{
|
||||
session()->put(
|
||||
self::LAST_LOGIN_ATTEMPTED_SESSION_KEY,
|
||||
|
@@ -51,7 +51,7 @@ class Saml2Service
|
||||
* Returns the SAML2 request ID, and the URL to redirect the user to.
|
||||
*
|
||||
* @throws Error
|
||||
* @returns array{url: string, id: ?string}
|
||||
* @return array{url: string, id: ?string}
|
||||
*/
|
||||
public function logout(User $user): array
|
||||
{
|
||||
|
@@ -55,7 +55,7 @@ class SocialDriverManager
|
||||
|
||||
/**
|
||||
* Gets the names of the active social drivers, keyed by driver id.
|
||||
* @returns array<string, string>
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getActive(): array
|
||||
{
|
||||
|
@@ -4,6 +4,8 @@ namespace BookStack\Activity\Models;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||
use BookStack\Users\Models\OwnableInterface;
|
||||
use BookStack\Users\Models\User;
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -17,12 +19,10 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
* @property int $local_id
|
||||
* @property string $entity_type
|
||||
* @property int $entity_id
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $content_ref
|
||||
* @property bool $archived
|
||||
*/
|
||||
class Comment extends Model implements Loggable
|
||||
class Comment extends Model implements Loggable, OwnableInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasCreatorAndUpdater;
|
||||
@@ -39,6 +39,7 @@ class Comment extends Model implements Loggable
|
||||
|
||||
/**
|
||||
* Get the parent comment this is in reply to (if existing).
|
||||
* @return BelongsTo<Comment, $this>
|
||||
*/
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
|
@@ -36,7 +36,7 @@ class WatchLevels
|
||||
|
||||
/**
|
||||
* Get all the possible values as an option_name => value array.
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
@@ -50,7 +50,7 @@ class WatchLevels
|
||||
|
||||
/**
|
||||
* Get the watch options suited for the given entity.
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public static function allSuitedFor(Entity $entity): array
|
||||
{
|
||||
|
@@ -8,7 +8,7 @@ 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.
|
||||
* Used in areas where no mutations are required, but performance is critical.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@@ -59,8 +59,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Auth::provider('external-users', function ($app, array $config) {
|
||||
return new ExternalBaseUserProvider($config['model']);
|
||||
Auth::provider('external-users', function () {
|
||||
return new ExternalBaseUserProvider();
|
||||
});
|
||||
|
||||
// Bind and provide the default system user as a singleton to the app instance when needed.
|
||||
|
@@ -15,7 +15,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
/**
|
||||
* The event listener mappings for the application.
|
||||
*
|
||||
* @var array<class-string, array<int, class-string>>
|
||||
* @var array<class-string, array<int, string>>
|
||||
*/
|
||||
protected $listen = [
|
||||
SocialiteWasCalled::class => [
|
||||
|
@@ -5,11 +5,8 @@ namespace BookStack\App;
|
||||
/**
|
||||
* Assigned to models that can have slugs.
|
||||
* Must have the below properties.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
*/
|
||||
interface Sluggable
|
||||
interface SluggableInterface
|
||||
{
|
||||
/**
|
||||
* Regenerate the slug for this model.
|
@@ -52,7 +52,7 @@ class UpdateUrlCommand extends Command
|
||||
'page_revisions' => ['html', 'text', 'markdown'],
|
||||
'images' => ['url'],
|
||||
'settings' => ['value'],
|
||||
'comments' => ['html', 'text'],
|
||||
'comments' => ['html'],
|
||||
];
|
||||
|
||||
foreach ($columnsToUpdateByTable as $table => $columns) {
|
||||
|
@@ -98,7 +98,7 @@ class PageRevisionController extends Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
$prev = $revision->getPrevious();
|
||||
$prev = $revision->getPreviousRevision();
|
||||
$prevContent = $prev->html ?? '';
|
||||
$diff = Diff::excecute($prevContent, $revision->html);
|
||||
|
||||
|
@@ -6,10 +6,11 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Deletion;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\DeletionRepo;
|
||||
use BookStack\Http\ApiController;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class RecycleBinApiController extends ApiController
|
||||
{
|
||||
@@ -40,7 +41,7 @@ class RecycleBinApiController extends ApiController
|
||||
'updated_at',
|
||||
'deletable_type',
|
||||
'deletable_id',
|
||||
], [Closure::fromCallable([$this, 'listFormatter'])]);
|
||||
], [$this->listFormatter(...)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,10 +70,9 @@ class RecycleBinApiController extends ApiController
|
||||
/**
|
||||
* Load some related details for the deletion listing.
|
||||
*/
|
||||
protected function listFormatter(Deletion $deletion)
|
||||
protected function listFormatter(Deletion $deletion): void
|
||||
{
|
||||
$deletable = $deletion->deletable;
|
||||
$withTrashedQuery = fn (Builder $query) => $query->withTrashed();
|
||||
|
||||
if ($deletable instanceof BookChild) {
|
||||
$parent = $deletable->getParent();
|
||||
@@ -81,11 +81,19 @@ class RecycleBinApiController extends ApiController
|
||||
}
|
||||
|
||||
if ($deletable instanceof Book || $deletable instanceof Chapter) {
|
||||
$countsToLoad = ['pages' => $withTrashedQuery];
|
||||
$countsToLoad = ['pages' => static::withTrashedQuery(...)];
|
||||
if ($deletable instanceof Book) {
|
||||
$countsToLoad['chapters'] = $withTrashedQuery;
|
||||
$countsToLoad['chapters'] = static::withTrashedQuery(...);
|
||||
}
|
||||
$deletable->loadCount($countsToLoad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<Chapter|Page> $query
|
||||
*/
|
||||
protected static function withTrashedQuery(Builder $query): void
|
||||
{
|
||||
$query->withTrashed();
|
||||
}
|
||||
}
|
||||
|
@@ -26,10 +26,10 @@ use Illuminate\Support\Collection;
|
||||
* @property ?Page $defaultTemplate
|
||||
* @property ?SortRule $sortRule
|
||||
*/
|
||||
class Book extends Entity implements HasCoverImage
|
||||
class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
use HtmlDescriptionTrait;
|
||||
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
@@ -95,6 +95,7 @@ class Book extends Entity implements HasCoverImage
|
||||
|
||||
/**
|
||||
* Get all pages within this book.
|
||||
* @return HasMany<Page, $this>
|
||||
*/
|
||||
public function pages(): HasMany
|
||||
{
|
||||
@@ -111,6 +112,7 @@ class Book extends Entity implements HasCoverImage
|
||||
|
||||
/**
|
||||
* Get all chapters within this book.
|
||||
* @return HasMany<Chapter, $this>
|
||||
*/
|
||||
public function chapters(): HasMany
|
||||
{
|
||||
|
@@ -8,10 +8,10 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Bookshelf extends Entity implements HasCoverImage
|
||||
class Bookshelf extends Entity implements CoverImageInterface, HtmlDescriptionInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
use HtmlDescriptionTrait;
|
||||
|
||||
protected $table = 'bookshelves';
|
||||
|
||||
@@ -70,6 +70,7 @@ class Bookshelf extends Entity implements HasCoverImage
|
||||
|
||||
/**
|
||||
* Get the cover image of the shelf.
|
||||
* @return BelongsTo<Image, $this>
|
||||
*/
|
||||
public function cover(): BelongsTo
|
||||
{
|
||||
|
@@ -14,10 +14,10 @@ use Illuminate\Support\Collection;
|
||||
* @property ?int $default_template_id
|
||||
* @property ?Page $defaultTemplate
|
||||
*/
|
||||
class Chapter extends BookChild
|
||||
class Chapter extends BookChild implements HtmlDescriptionInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
use HtmlDescriptionTrait;
|
||||
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
@@ -27,7 +27,7 @@ class Chapter extends BookChild
|
||||
/**
|
||||
* Get the pages that this chapter contains.
|
||||
*
|
||||
* @return HasMany<Page>
|
||||
* @return HasMany<Page, $this>
|
||||
*/
|
||||
public function pages(string $dir = 'ASC'): HasMany
|
||||
{
|
||||
@@ -60,7 +60,7 @@ class Chapter extends BookChild
|
||||
|
||||
/**
|
||||
* Get the visible pages in this chapter.
|
||||
* @returns Collection<Page>
|
||||
* @return Collection<Page>
|
||||
*/
|
||||
public function getVisiblePages(): Collection
|
||||
{
|
||||
|
@@ -4,7 +4,7 @@ namespace BookStack\Entities\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
interface HasCoverImage
|
||||
interface CoverImageInterface
|
||||
{
|
||||
/**
|
||||
* Get the cover image for this item.
|
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
* A model that can be deleted in a manner that deletions
|
||||
* are tracked to be part of the recycle bin system.
|
||||
*/
|
||||
interface Deletable
|
||||
interface DeletableInterface
|
||||
{
|
||||
public function deletions(): MorphMany;
|
||||
}
|
@@ -13,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
* @property int $deleted_by
|
||||
* @property string $deletable_type
|
||||
* @property int $deletable_id
|
||||
* @property Deletable $deletable
|
||||
* @property DeletableInterface $deletable
|
||||
*/
|
||||
class Deletion extends Model implements Loggable
|
||||
{
|
||||
|
@@ -12,7 +12,7 @@ use BookStack\Activity\Models\View;
|
||||
use BookStack\Activity\Models\Viewable;
|
||||
use BookStack\Activity\Models\Watch;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\App\Sluggable;
|
||||
use BookStack\App\SluggableInterface;
|
||||
use BookStack\Entities\Tools\SlugGenerator;
|
||||
use BookStack\Permissions\JointPermissionBuilder;
|
||||
use BookStack\Permissions\Models\EntityPermission;
|
||||
@@ -22,7 +22,8 @@ use BookStack\References\Reference;
|
||||
use BookStack\Search\SearchIndex;
|
||||
use BookStack\Search\SearchTerm;
|
||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||
use BookStack\Users\Models\HasOwner;
|
||||
use BookStack\Users\Models\OwnableInterface;
|
||||
use BookStack\Users\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
@@ -43,17 +44,23 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @property Carbon $deleted_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property int $owned_by
|
||||
* @property Collection $tags
|
||||
*
|
||||
* @method static Entity|Builder visible()
|
||||
* @method static Builder withLastView()
|
||||
* @method static Builder withViewCount()
|
||||
*/
|
||||
abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, Deletable, Loggable
|
||||
abstract class Entity extends Model implements
|
||||
SluggableInterface,
|
||||
Favouritable,
|
||||
Viewable,
|
||||
DeletableInterface,
|
||||
OwnableInterface,
|
||||
Loggable
|
||||
{
|
||||
use SoftDeletes;
|
||||
use HasCreatorAndUpdater;
|
||||
use HasOwner;
|
||||
|
||||
/**
|
||||
* @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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -318,7 +339,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
|
||||
/**
|
||||
* @property string $description
|
||||
* @property string $description_html
|
||||
*/
|
||||
trait HasHtmlDescription
|
||||
{
|
||||
/**
|
||||
* Get the HTML description for this book.
|
||||
*/
|
||||
public function descriptionHtml(): string
|
||||
{
|
||||
$html = $this->description_html ?: '<p>' . nl2br(e($this->description)) . '</p>';
|
||||
return HtmlContentFilter::removeScriptsFromHtmlString($html);
|
||||
}
|
||||
}
|
17
app/Entities/Models/HtmlDescriptionInterface.php
Normal file
17
app/Entities/Models/HtmlDescriptionInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
interface HtmlDescriptionInterface
|
||||
{
|
||||
/**
|
||||
* Get the HTML-based description for this item.
|
||||
* By default, the content should be sanitised unless raw is set to true.
|
||||
*/
|
||||
public function descriptionHtml(bool $raw = false): string;
|
||||
|
||||
/**
|
||||
* Set the HTML-based description for this item.
|
||||
*/
|
||||
public function setDescriptionHtml(string $html, string|null $plaintext = null): void;
|
||||
}
|
35
app/Entities/Models/HtmlDescriptionTrait.php
Normal file
35
app/Entities/Models/HtmlDescriptionTrait.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
|
||||
/**
|
||||
* @property string $description
|
||||
* @property string $description_html
|
||||
*/
|
||||
trait HtmlDescriptionTrait
|
||||
{
|
||||
public function descriptionHtml(bool $raw = false): string
|
||||
{
|
||||
$html = $this->description_html ?: '<p>' . nl2br(e($this->description)) . '</p>';
|
||||
if ($raw) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return HtmlContentFilter::removeScriptsFromHtmlString($html);
|
||||
}
|
||||
|
||||
public function setDescriptionHtml(string $html, string|null $plaintext = null): void
|
||||
{
|
||||
$this->description_html = $html;
|
||||
|
||||
if ($plaintext !== null) {
|
||||
$this->description = $plaintext;
|
||||
}
|
||||
|
||||
if (empty($html) && !empty($plaintext)) {
|
||||
$this->description_html = $this->descriptionHtml();
|
||||
}
|
||||
}
|
||||
}
|
@@ -60,7 +60,7 @@ class PageRevision extends Model implements Loggable
|
||||
/**
|
||||
* Get the previous revision for the same page if existing.
|
||||
*/
|
||||
public function getPrevious(): ?PageRevision
|
||||
public function getPreviousRevision(): ?PageRevision
|
||||
{
|
||||
$id = static::newQuery()->where('page_id', '=', $this->page_id)
|
||||
->where('id', '<', $this->id)
|
||||
|
@@ -6,6 +6,9 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @implements ProvidesEntityQueries<Book>
|
||||
*/
|
||||
class BookQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $listAttributes = [
|
||||
@@ -13,6 +16,9 @@ class BookQueries implements ProvidesEntityQueries
|
||||
'created_at', 'updated_at', 'image_id', 'owned_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return Builder<Book>
|
||||
*/
|
||||
public function start(): Builder
|
||||
{
|
||||
return Book::query();
|
||||
|
@@ -6,6 +6,9 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @implements ProvidesEntityQueries<Bookshelf>
|
||||
*/
|
||||
class BookshelfQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $listAttributes = [
|
||||
@@ -13,6 +16,9 @@ class BookshelfQueries implements ProvidesEntityQueries
|
||||
'created_at', 'updated_at', 'image_id', 'owned_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return Builder<Bookshelf>
|
||||
*/
|
||||
public function start(): Builder
|
||||
{
|
||||
return Bookshelf::query();
|
||||
|
@@ -6,6 +6,9 @@ use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @implements ProvidesEntityQueries<Chapter>
|
||||
*/
|
||||
class ChapterQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $listAttributes = [
|
||||
|
@@ -35,6 +35,7 @@ class EntityQueries
|
||||
/**
|
||||
* Start a query of visible entities of the given type,
|
||||
* suitable for listing display.
|
||||
* @return Builder<Entity>
|
||||
*/
|
||||
public function visibleForList(string $entityType): Builder
|
||||
{
|
||||
@@ -44,7 +45,6 @@ class EntityQueries
|
||||
|
||||
protected function getQueriesForType(string $type): ProvidesEntityQueries
|
||||
{
|
||||
/** @var ?ProvidesEntityQueries $queries */
|
||||
$queries = match ($type) {
|
||||
'page' => $this->pages,
|
||||
'chapter' => $this->chapters,
|
||||
|
@@ -6,6 +6,9 @@ use BookStack\Entities\Models\Page;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @implements ProvidesEntityQueries<Page>
|
||||
*/
|
||||
class PageQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $contentAttributes = [
|
||||
@@ -18,6 +21,9 @@ class PageQueries implements ProvidesEntityQueries
|
||||
'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return Builder<Page>
|
||||
*/
|
||||
public function start(): Builder
|
||||
{
|
||||
return Page::query();
|
||||
@@ -66,6 +72,9 @@ class PageQueries implements ProvidesEntityQueries
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<Page>
|
||||
*/
|
||||
public function visibleForList(): Builder
|
||||
{
|
||||
return $this->start()
|
||||
|
@@ -7,28 +7,32 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* Interface for our classes which provide common queries for our
|
||||
* entity objects. Ideally all queries for entities should run through
|
||||
* entity objects. Ideally, all queries for entities should run through
|
||||
* these classes.
|
||||
* Any added methods should return a builder instances to allow extension
|
||||
* via building on the query, unless the method starts with 'find'
|
||||
* in which case an entity object should be returned.
|
||||
* (nullable unless it's a *OrFail method).
|
||||
*
|
||||
* @template TModel of Entity
|
||||
*/
|
||||
interface ProvidesEntityQueries
|
||||
{
|
||||
/**
|
||||
* Start a new query for this entity type.
|
||||
* @return Builder<TModel>
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Start a query for items that are visible, with selection
|
||||
* configured for list display of this item.
|
||||
* @return Builder<TModel>
|
||||
*/
|
||||
public function visibleForList(): Builder;
|
||||
}
|
||||
|
@@ -7,8 +7,9 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\HasHtmlDescription;
|
||||
use BookStack\Entities\Models\CoverImageInterface;
|
||||
use BookStack\Entities\Models\HtmlDescriptionInterface;
|
||||
use BookStack\Entities\Models\HtmlDescriptionTrait;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\References\ReferenceStore;
|
||||
@@ -88,12 +89,10 @@ class BaseRepo
|
||||
/**
|
||||
* Update the given items' cover image, or clear it.
|
||||
*
|
||||
* @param Entity&HasCoverImage $entity
|
||||
*
|
||||
* @throws ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateCoverImage($entity, ?UploadedFile $coverImage, bool $removeImage = false)
|
||||
public function updateCoverImage(Entity&CoverImageInterface $entity, ?UploadedFile $coverImage, bool $removeImage = false)
|
||||
{
|
||||
if ($coverImage) {
|
||||
$imageType = $entity->coverImageTypeKey();
|
||||
@@ -105,7 +104,7 @@ class BaseRepo
|
||||
|
||||
if ($removeImage) {
|
||||
$this->imageRepo->destroyImage($entity->cover()->first());
|
||||
$entity->image_id = 0;
|
||||
$entity->cover()->dissociate();
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
@@ -150,18 +149,17 @@ class BaseRepo
|
||||
|
||||
protected function updateDescription(Entity $entity, array $input): void
|
||||
{
|
||||
if (!in_array(HasHtmlDescription::class, class_uses($entity))) {
|
||||
if (!($entity instanceof HtmlDescriptionInterface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var HasHtmlDescription $entity */
|
||||
if (isset($input['description_html'])) {
|
||||
$entity->description_html = HtmlDescriptionFilter::filterFromString($input['description_html']);
|
||||
$entity->description = html_entity_decode(strip_tags($input['description_html']));
|
||||
$entity->setDescriptionHtml(
|
||||
HtmlDescriptionFilter::filterFromString($input['description_html']),
|
||||
html_entity_decode(strip_tags($input['description_html']))
|
||||
);
|
||||
} else if (isset($input['description'])) {
|
||||
$entity->description = $input['description'];
|
||||
$entity->description_html = '';
|
||||
$entity->description_html = $entity->descriptionHtml();
|
||||
$entity->setDescriptionHtml('', $input['description']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\CoverImageInterface;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Repos\ChapterRepo;
|
||||
@@ -105,7 +105,7 @@ class Cloner
|
||||
$inputData['tags'] = $this->entityTagsToInputArray($entity);
|
||||
|
||||
// Add a cover to the data if existing on the original entity
|
||||
if ($entity instanceof HasCoverImage) {
|
||||
if ($entity instanceof CoverImageInterface) {
|
||||
$cover = $entity->cover()->first();
|
||||
if ($cover) {
|
||||
$inputData['image'] = $this->imageToUploadedFile($cover);
|
||||
|
@@ -7,15 +7,14 @@ use Closure;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
|
||||
class PageIncludeParser
|
||||
{
|
||||
protected static string $includeTagRegex = "/{{@\s?([0-9].*?)}}/";
|
||||
|
||||
/**
|
||||
* Elements to clean up and remove if left empty after a parsing operation.
|
||||
* @var DOMElement[]
|
||||
* Nodes to clean up and remove if left empty after a parsing operation.
|
||||
* @var DOMNode[]
|
||||
*/
|
||||
protected array $toCleanup = [];
|
||||
|
||||
@@ -159,7 +158,7 @@ class PageIncludeParser
|
||||
|
||||
/**
|
||||
* Splits the given $parentNode at the location of the $domNode within it.
|
||||
* Attempts replicate the original $parentNode, moving some of their parent
|
||||
* Attempts to replicate the original $parentNode, moving some of their parent
|
||||
* children in where needed, before adding the $domNode between.
|
||||
*/
|
||||
protected function splitNodeAtChildNode(DOMElement $parentNode, DOMNode $domNode): void
|
||||
@@ -171,6 +170,10 @@ class PageIncludeParser
|
||||
}
|
||||
|
||||
$parentClone = $parentNode->cloneNode();
|
||||
if (!($parentClone instanceof DOMElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parentNode->parentNode->insertBefore($parentClone, $parentNode);
|
||||
$parentClone->removeAttribute('id');
|
||||
|
||||
@@ -203,7 +206,7 @@ class PageIncludeParser
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup after a parse operation.
|
||||
* Clean up after a parse operation.
|
||||
* Removes stranded elements we may have left during the parse.
|
||||
*/
|
||||
protected function cleanup(): void
|
||||
|
@@ -3,7 +3,7 @@
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\App\Sluggable;
|
||||
use BookStack\App\SluggableInterface;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -13,9 +13,9 @@ class SlugGenerator
|
||||
* Generate a fresh slug for the given entity.
|
||||
* 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)) {
|
||||
$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
|
||||
{
|
||||
@@ -39,10 +39,8 @@ 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
|
||||
protected function slugInUse(string $slug, SluggableInterface&Model $model): bool
|
||||
{
|
||||
$query = $model->newQuery()->where('slug', '=', $slug);
|
||||
|
||||
|
@@ -8,7 +8,7 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Deletion;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\CoverImageInterface;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
@@ -398,7 +398,7 @@ class TrashCan
|
||||
$entity->referencesTo()->delete();
|
||||
$entity->referencesFrom()->delete();
|
||||
|
||||
if ($entity instanceof HasCoverImage && $entity->cover()->exists()) {
|
||||
if ($entity instanceof CoverImageInterface && $entity->cover()->exists()) {
|
||||
$imageService = app()->make(ImageService::class);
|
||||
$imageService->destroy($entity->cover()->first());
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
@@ -12,6 +11,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Throwable;
|
||||
|
||||
@@ -20,7 +20,7 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
* @var array<int, class-string<Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
NotFoundException::class,
|
||||
@@ -50,11 +50,11 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
* @param \Throwable $exception
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return void
|
||||
*@throws Throwable
|
||||
*
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
@@ -64,12 +64,9 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param Exception $e
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param Request $request
|
||||
*/
|
||||
public function render($request, Throwable $e)
|
||||
public function render($request, Throwable $e): SymfonyResponse
|
||||
{
|
||||
if ($e instanceof FatalError && str_contains($e->getMessage(), 'bytes exhausted (tried to allocate') && $this->onOutOfMemory) {
|
||||
$response = call_user_func($this->onOutOfMemory);
|
||||
@@ -94,7 +91,7 @@ class Handler extends ExceptionHandler
|
||||
* If the callable returns a response, this response will be returned
|
||||
* to the request upon error.
|
||||
*/
|
||||
public function prepareForOutOfMemory(callable $onOutOfMemory)
|
||||
public function prepareForOutOfMemory(callable $onOutOfMemory): void
|
||||
{
|
||||
$this->onOutOfMemory = $onOutOfMemory;
|
||||
}
|
||||
@@ -102,7 +99,7 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Forget the current out of memory handler, if existing.
|
||||
*/
|
||||
public function forgetOutOfMemoryHandler()
|
||||
public function forgetOutOfMemoryHandler(): void
|
||||
{
|
||||
$this->onOutOfMemory = null;
|
||||
}
|
||||
@@ -152,12 +149,9 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Auth\AuthenticationException $exception
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
protected function unauthenticated($request, AuthenticationException $exception): SymfonyResponse
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthenticated.'], 401);
|
||||
@@ -169,12 +163,9 @@ class Handler extends ExceptionHandler
|
||||
/**
|
||||
* Convert a validation exception into a JSON response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Validation\ValidationException $exception
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function invalidJson($request, ValidationException $exception)
|
||||
protected function invalidJson($request, ValidationException $exception): JsonResponse
|
||||
{
|
||||
return response()->json($exception->errors(), $exception->status);
|
||||
}
|
||||
|
@@ -39,6 +39,9 @@ class ImportRepo
|
||||
return $this->queryVisible()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder<Import>
|
||||
*/
|
||||
public function queryVisible(): Builder
|
||||
{
|
||||
$query = Import::query();
|
||||
|
@@ -6,7 +6,7 @@ use BookStack\Exports\ZipExports\ZipExportFiles;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
use BookStack\Uploads\Attachment;
|
||||
|
||||
class ZipExportAttachment extends ZipExportModel
|
||||
final class ZipExportAttachment extends ZipExportModel
|
||||
{
|
||||
public ?int $id = null;
|
||||
public string $name;
|
||||
@@ -52,9 +52,9 @@ class ZipExportAttachment extends ZipExportModel
|
||||
return $context->validateData($data, $rules);
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
$model = new self();
|
||||
$model = new static();
|
||||
|
||||
$model->id = $data['id'] ?? null;
|
||||
$model->name = $data['name'];
|
||||
|
@@ -8,7 +8,7 @@ use BookStack\Entities\Models\Page;
|
||||
use BookStack\Exports\ZipExports\ZipExportFiles;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
|
||||
class ZipExportBook extends ZipExportModel
|
||||
final class ZipExportBook extends ZipExportModel
|
||||
{
|
||||
public ?int $id = null;
|
||||
public string $name;
|
||||
@@ -101,9 +101,9 @@ class ZipExportBook extends ZipExportModel
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
$model = new self();
|
||||
$model = new static();
|
||||
|
||||
$model->id = $data['id'] ?? null;
|
||||
$model->name = $data['name'];
|
||||
|
@@ -7,7 +7,7 @@ use BookStack\Entities\Models\Page;
|
||||
use BookStack\Exports\ZipExports\ZipExportFiles;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
|
||||
class ZipExportChapter extends ZipExportModel
|
||||
final class ZipExportChapter extends ZipExportModel
|
||||
{
|
||||
public ?int $id = null;
|
||||
public string $name;
|
||||
@@ -79,9 +79,9 @@ class ZipExportChapter extends ZipExportModel
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
$model = new self();
|
||||
$model = new static();
|
||||
|
||||
$model->id = $data['id'] ?? null;
|
||||
$model->name = $data['name'];
|
||||
|
@@ -7,7 +7,7 @@ use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
use BookStack\Uploads\Image;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ZipExportImage extends ZipExportModel
|
||||
final class ZipExportImage extends ZipExportModel
|
||||
{
|
||||
public ?int $id = null;
|
||||
public string $name;
|
||||
@@ -43,9 +43,9 @@ class ZipExportImage extends ZipExportModel
|
||||
return $context->validateData($data, $rules);
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
$model = new self();
|
||||
$model = new static();
|
||||
|
||||
$model->id = $data['id'] ?? null;
|
||||
$model->name = $data['name'];
|
||||
|
@@ -30,12 +30,12 @@ abstract class ZipExportModel implements JsonSerializable
|
||||
/**
|
||||
* Decode the array of data into this export model.
|
||||
*/
|
||||
abstract public static function fromArray(array $data): self;
|
||||
abstract public static function fromArray(array $data): static;
|
||||
|
||||
/**
|
||||
* Decode an array of array data into an array of export models.
|
||||
* @param array[] $data
|
||||
* @return self[]
|
||||
* @return static[]
|
||||
*/
|
||||
public static function fromManyArray(array $data): array
|
||||
{
|
||||
|
@@ -7,7 +7,7 @@ use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Exports\ZipExports\ZipExportFiles;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
|
||||
class ZipExportPage extends ZipExportModel
|
||||
final class ZipExportPage extends ZipExportModel
|
||||
{
|
||||
public ?int $id = null;
|
||||
public string $name;
|
||||
@@ -86,9 +86,9 @@ class ZipExportPage extends ZipExportModel
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
$model = new self();
|
||||
$model = new static();
|
||||
|
||||
$model->id = $data['id'] ?? null;
|
||||
$model->name = $data['name'];
|
||||
|
@@ -5,7 +5,7 @@ namespace BookStack\Exports\ZipExports\Models;
|
||||
use BookStack\Activity\Models\Tag;
|
||||
use BookStack\Exports\ZipExports\ZipValidationHelper;
|
||||
|
||||
class ZipExportTag extends ZipExportModel
|
||||
final class ZipExportTag extends ZipExportModel
|
||||
{
|
||||
public string $name;
|
||||
public ?string $value = null;
|
||||
@@ -39,9 +39,9 @@ class ZipExportTag extends ZipExportModel
|
||||
return $context->validateData($data, $rules);
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
$model = new self();
|
||||
$model = new static();
|
||||
|
||||
$model->name = $data['name'];
|
||||
$model->value = $data['value'] ?? null;
|
||||
|
@@ -9,6 +9,8 @@ class Kernel extends HttpKernel
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var list<class-string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
\BookStack\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
@@ -21,7 +23,7 @@ class Kernel extends HttpKernel
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, array<int, class-string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
@@ -47,7 +49,7 @@ class Kernel extends HttpKernel
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, class-string>
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \BookStack\Http\Middleware\Authenticate::class,
|
||||
|
@@ -9,7 +9,7 @@ class EncryptCookies extends Middleware
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
|
@@ -9,7 +9,7 @@ class PreventRequestsDuringMaintenance extends Middleware
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
|
@@ -9,7 +9,7 @@ class TrimStrings extends Middleware
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'password',
|
||||
|
@@ -11,7 +11,7 @@ class TrustProxies extends Middleware
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int,string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
|
@@ -16,7 +16,7 @@ class VerifyCsrfToken extends Middleware
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'saml2/*',
|
||||
|
@@ -30,7 +30,7 @@ class EntityPermissionEvaluator
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, int>> $permitsByType
|
||||
* @param array<string, array<int, string>> $permitsByType
|
||||
*/
|
||||
protected function evaluatePermitsByType(array $permitsByType): ?int
|
||||
{
|
||||
@@ -50,7 +50,7 @@ class EntityPermissionEvaluator
|
||||
/**
|
||||
* @param string[] $typeIdChain
|
||||
* @param array<string, EntityPermission[]> $permissionMapByTypeId
|
||||
* @return array<string, array<string, int>>
|
||||
* @return array<string, array<int, string>>
|
||||
*/
|
||||
protected function collapseAndCategorisePermissions(array $typeIdChain, array $permissionMapByTypeId): array
|
||||
{
|
||||
|
@@ -7,8 +7,7 @@ use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Permissions\Models\EntityPermission;
|
||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||
use BookStack\Users\Models\HasOwner;
|
||||
use BookStack\Users\Models\OwnableInterface;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
@@ -24,10 +23,8 @@ class PermissionApplicator
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$action = $explodedPermission[1] ?? $explodedPermission[0];
|
||||
@@ -39,7 +36,7 @@ class PermissionApplicator
|
||||
$allRolePermission = $user->can($fullPermission . '-all');
|
||||
$ownRolePermission = $user->can($fullPermission . '-own');
|
||||
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
|
||||
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
|
||||
$ownerField = $ownable->getOwnerFieldName();
|
||||
$ownableFieldVal = $ownable->getAttribute($ownerField);
|
||||
|
||||
if (is_null($ownableFieldVal)) {
|
||||
@@ -49,11 +46,15 @@ class PermissionApplicator
|
||||
$isOwner = $user->id === $ownableFieldVal;
|
||||
$hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission);
|
||||
|
||||
// Handle non entity specific jointPermissions
|
||||
// Handle non-entity-specific jointPermissions
|
||||
if (in_array($explodedPermission[0], $nonJointPermissions)) {
|
||||
return $hasRolePermission;
|
||||
}
|
||||
|
||||
if (!($ownable instanceof Entity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
|
||||
|
||||
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
|
||||
|
@@ -48,7 +48,7 @@ class CrossLinkParser
|
||||
/**
|
||||
* Get a list of href values from the given document.
|
||||
*
|
||||
* @returns string[]
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getLinksFromContent(string $html): array
|
||||
{
|
||||
|
@@ -4,7 +4,8 @@ namespace BookStack\References;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasHtmlDescription;
|
||||
use BookStack\Entities\Models\HtmlDescriptionInterface;
|
||||
use BookStack\Entities\Models\HtmlDescriptionTrait;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\RevisionRepo;
|
||||
use BookStack\Util\HtmlDocument;
|
||||
@@ -61,20 +62,18 @@ class ReferenceUpdater
|
||||
{
|
||||
if ($entity instanceof Page) {
|
||||
$this->updateReferencesWithinPage($entity, $oldLink, $newLink);
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array(HasHtmlDescription::class, class_uses($entity))) {
|
||||
if ($entity instanceof HtmlDescriptionInterface) {
|
||||
$this->updateReferencesWithinDescription($entity, $oldLink, $newLink);
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateReferencesWithinDescription(Entity $entity, string $oldLink, string $newLink): void
|
||||
protected function updateReferencesWithinDescription(Entity&HtmlDescriptionInterface $entity, string $oldLink, string $newLink): void
|
||||
{
|
||||
/** @var HasHtmlDescription&Entity $entity */
|
||||
$entity = (clone $entity)->refresh();
|
||||
$html = $this->updateLinksInHtml($entity->description_html ?: '', $oldLink, $newLink);
|
||||
$entity->description_html = $html;
|
||||
$html = $this->updateLinksInHtml($entity->descriptionHtml(true) ?: '', $oldLink, $newLink);
|
||||
$entity->setDescriptionHtml($html);
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
|
@@ -119,7 +119,7 @@ class SearchIndex
|
||||
* Create a scored term array from the given text, where the keys are the terms
|
||||
* and the values are their scores.
|
||||
*
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
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
|
||||
* and the values are their scores.
|
||||
*
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
protected function generateTermScoreMapFromHtml(string $html): array
|
||||
{
|
||||
@@ -177,7 +177,7 @@ class SearchIndex
|
||||
*
|
||||
* @param Tag[] $tags
|
||||
*
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
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
|
||||
* and the values are the frequency of that term.
|
||||
*
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
protected function textToTermCountMap(string $text): array
|
||||
{
|
||||
@@ -243,7 +243,7 @@ class SearchIndex
|
||||
* For the given entity, Generate an array of term data details.
|
||||
* 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
|
||||
{
|
||||
@@ -279,7 +279,7 @@ class SearchIndex
|
||||
*
|
||||
* @param array<string, int>[] ...$scoreMaps
|
||||
*
|
||||
* @returns array<string, int>
|
||||
* @return array<string, int>
|
||||
*/
|
||||
protected function mergeTermScoreMaps(...$scoreMaps): array
|
||||
{
|
||||
|
@@ -14,6 +14,9 @@ class SearchOptionSet
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
/**
|
||||
* @param T[] $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = $options;
|
||||
|
@@ -285,7 +285,7 @@ class SearchRunner
|
||||
*
|
||||
* @param array<string, int> $termCounts
|
||||
*
|
||||
* @return array<string, int>
|
||||
* @return array<string, float>
|
||||
*/
|
||||
protected function rawTermCountsToAdjustments(array $termCounts): array
|
||||
{
|
||||
|
@@ -78,7 +78,7 @@ class BookSorter
|
||||
* Sort the books content using the given sort map.
|
||||
* Returns a list of books that were involved in the operation.
|
||||
*
|
||||
* @returns Book[]
|
||||
* @return Book[]
|
||||
*/
|
||||
public function sortUsingMap(BookSortMap $sortMap): array
|
||||
{
|
||||
|
@@ -29,7 +29,7 @@ class SortRuleController extends Controller
|
||||
|
||||
$operations = SortRuleOperation::fromSequence($request->input('sequence'));
|
||||
if (count($operations) === 0) {
|
||||
return redirect()->withInput()->withErrors(['sequence' => 'No operations set.']);
|
||||
return redirect('/settings/sorting/rules/new')->withInput()->withErrors(['sequence' => 'No operations set.']);
|
||||
}
|
||||
|
||||
$rule = new SortRule();
|
||||
|
@@ -62,7 +62,7 @@ class ThemeEvents
|
||||
*
|
||||
* @param string $authSystem
|
||||
* @param array $userData
|
||||
* @returns bool|null
|
||||
* @return bool|null
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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';
|
||||
|
||||
@@ -96,7 +96,7 @@ class ThemeEvents
|
||||
*
|
||||
* @param array $idTokenData
|
||||
* @param array $accessTokenData
|
||||
* @returns array|null
|
||||
* @return array|null
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @returns \Illuminate\Http\Response|null
|
||||
* @return \Illuminate\Http\Response|null
|
||||
*/
|
||||
const WEB_MIDDLEWARE_BEFORE = 'web_middleware_before';
|
||||
|
||||
@@ -154,7 +154,7 @@ class ThemeEvents
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @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';
|
||||
|
||||
@@ -173,7 +173,7 @@ class ThemeEvents
|
||||
* @param string|\BookStack\Activity\Models\Loggable $detail
|
||||
* @param \BookStack\Users\Models\User $initiator
|
||||
* @param int $initiatedTime
|
||||
* @returns array|null
|
||||
* @return array|null
|
||||
*/
|
||||
const WEBHOOK_CALL_BEFORE = 'webhook_call_before';
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ use BookStack\Entities\Models\Page;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||
use BookStack\Users\Models\OwnableInterface;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -27,7 +28,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*
|
||||
* @method static Entity|Builder visible()
|
||||
*/
|
||||
class Attachment extends Model
|
||||
class Attachment extends Model implements OwnableInterface
|
||||
{
|
||||
use HasCreatorAndUpdater;
|
||||
use HasFactory;
|
||||
|
@@ -146,10 +146,10 @@ class ImageGalleryApiController extends ApiController
|
||||
$data['content']['html'] = "<div drawio-diagram=\"{$image->id}\"><img src=\"{$escapedUrl}\"></div>";
|
||||
$data['content']['markdown'] = $data['content']['html'];
|
||||
} else {
|
||||
$escapedDisplayThumb = htmlentities($image->thumbs['display']);
|
||||
$escapedDisplayThumb = htmlentities($image->getAttribute('thumbs')['display']);
|
||||
$data['content']['html'] = "<a href=\"{$escapedUrl}\" target=\"_blank\"><img src=\"{$escapedDisplayThumb}\" alt=\"{$escapedName}\"></a>";
|
||||
$mdEscapedName = str_replace(']', '', str_replace('[', '', $image->name));
|
||||
$mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->thumbs['display']));
|
||||
$mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->getAttribute('thumbs')['display']));
|
||||
$data['content']['markdown'] = "";
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Page;
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Users\Models\HasCreatorAndUpdater;
|
||||
use BookStack\Users\Models\OwnableInterface;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@@ -21,7 +22,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
*/
|
||||
class Image extends Model
|
||||
class Image extends Model implements OwnableInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use HasCreatorAndUpdater;
|
||||
|
@@ -138,7 +138,7 @@ class ImageService
|
||||
* Get the raw data content from an image.
|
||||
*
|
||||
* @throws Exception
|
||||
* @returns ?resource
|
||||
* @return ?resource
|
||||
*/
|
||||
public function getImageStream(Image $image): mixed
|
||||
{
|
||||
|
@@ -61,7 +61,7 @@ class ImageStorageDisk
|
||||
|
||||
/**
|
||||
* Get a stream to the file at the given path.
|
||||
* @returns ?resource
|
||||
* @return ?resource
|
||||
*/
|
||||
public function stream(string $path): mixed
|
||||
{
|
||||
|
@@ -81,7 +81,7 @@ class UserApiController extends ApiController
|
||||
return $this->apiListingResponse($users, [
|
||||
'id', 'name', 'slug', 'email', 'external_auth_id',
|
||||
'created_at', 'updated_at', 'last_activity_at',
|
||||
], [Closure::fromCallable([$this, 'listFormatter'])]);
|
||||
], [$this->listFormatter(...)]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -27,4 +27,9 @@ trait HasCreatorAndUpdater
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
|
||||
public function getOwnerFieldName(): string
|
||||
{
|
||||
return 'created_by';
|
||||
}
|
||||
}
|
||||
|
@@ -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');
|
||||
}
|
||||
}
|
8
app/Users/Models/OwnableInterface.php
Normal file
8
app/Users/Models/OwnableInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Users\Models;
|
||||
|
||||
interface OwnableInterface
|
||||
{
|
||||
public function getOwnerFieldName(): string;
|
||||
}
|
@@ -53,6 +53,7 @@ class Role extends Model implements Loggable
|
||||
|
||||
/**
|
||||
* The RolePermissions that belong to the role.
|
||||
* @return BelongsToMany<RolePermission, $this>
|
||||
*/
|
||||
public function permissions(): BelongsToMany
|
||||
{
|
||||
|
@@ -10,7 +10,7 @@ use BookStack\Activity\Models\Loggable;
|
||||
use BookStack\Activity\Models\Watch;
|
||||
use BookStack\Api\ApiToken;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\App\Sluggable;
|
||||
use BookStack\App\SluggableInterface;
|
||||
use BookStack\Entities\Tools\SlugGenerator;
|
||||
use BookStack\Translation\LocaleDefinition;
|
||||
use BookStack\Translation\LocaleManager;
|
||||
@@ -26,6 +26,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -47,7 +48,7 @@ use Illuminate\Support\Collection;
|
||||
* @property Collection $mfaValues
|
||||
* @property ?Image $avatar
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
|
||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, SluggableInterface
|
||||
{
|
||||
use HasFactory;
|
||||
use Authenticatable;
|
||||
@@ -64,7 +65,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = ['name', 'email'];
|
||||
|
||||
@@ -73,7 +74,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*
|
||||
* @var array
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email',
|
||||
@@ -118,14 +119,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
/**
|
||||
* The roles that belong to the user.
|
||||
*
|
||||
* @return BelongsToMany
|
||||
* @return BelongsToMany<Role, $this>
|
||||
*/
|
||||
public function roles()
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
if ($this->id === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->belongsToMany(Role::class);
|
||||
}
|
||||
|
||||
@@ -372,7 +369,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ class OutOfMemoryHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the handler so no action is taken place on out of memory.
|
||||
* Forget the handler, so no action is taken place on out of memory.
|
||||
*/
|
||||
public function forget(): void
|
||||
{
|
||||
@@ -53,6 +53,11 @@ class OutOfMemoryHandler
|
||||
|
||||
protected function getHandler(): Handler
|
||||
{
|
||||
/**
|
||||
* We want to resolve our specific BookStack handling via the set app handler
|
||||
* singleton, but phpstan will only infer based on the interface.
|
||||
* @phpstan-ignore return.type
|
||||
*/
|
||||
return app()->make(ExceptionHandler::class);
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<arg name="parallel" value="75"/>
|
||||
<arg value="np"/>
|
||||
|
||||
<rule ref="PSR12"/>
|
||||
<rule ref="PSR12"/>
|
||||
|
||||
<rule ref="PSR1.Methods.CamelCapsMethodName">
|
||||
<exclude-pattern>./tests/*</exclude-pattern>
|
||||
|
@@ -7,7 +7,7 @@ parameters:
|
||||
- app
|
||||
|
||||
# The level 8 is the highest level
|
||||
level: 1
|
||||
level: 3
|
||||
|
||||
phpVersion:
|
||||
min: 80200
|
||||
|
@@ -27,7 +27,6 @@ class CommentStoreTest extends TestCase
|
||||
'local_id' => 1,
|
||||
'entity_id' => $page->id,
|
||||
'entity_type' => Page::newModelInstance()->getMorphClass(),
|
||||
'text' => null,
|
||||
'parent_id' => 2,
|
||||
]);
|
||||
|
||||
|
Reference in New Issue
Block a user